@graftland/cli 0.1.0 → 0.2.0-beta.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/README.md CHANGED
@@ -1,13 +1,18 @@
1
1
  # @graftland/cli
2
2
 
3
+ English |
4
+ [简体中文](https://github.com/graftland/graft/blob/main/packages/cli/README.zh-CN.md)
5
+ |
6
+ [繁體中文](https://github.com/graftland/graft/blob/main/packages/cli/README.zh-TW.md)
7
+
3
8
  The `graft` command: `start`, `create`, `add` and `remove`.
4
9
 
5
10
  ```sh
6
11
  npm install @graftland/cli effect
7
12
  ```
8
13
 
9
- Create a project with `npx @graftland/cli create my-app --install`, then run it
10
- with `npx graft start --watch`. Needs Node.js 22.15 or later.
14
+ Create a project with `npx @graftland/cli create my-app --install`. Then run it
15
+ with `npx graft start --watch`. You need Node.js 22.19 or later.
11
16
 
12
17
  ## Documentation
13
18
 
@@ -0,0 +1,22 @@
1
+ # @graftland/cli
2
+
3
+ [English](https://github.com/graftland/graft/blob/main/packages/cli/README.md)
4
+ | 简体中文 |
5
+ [繁體中文](https://github.com/graftland/graft/blob/main/packages/cli/README.zh-TW.md)
6
+
7
+ `graft` 命令:`start`、`create`、`add` 和 `remove`。
8
+
9
+ ```sh
10
+ npm install @graftland/cli effect
11
+ ```
12
+
13
+ 用 `npx @graftland/cli create my-app --install` 创建项目,然后用
14
+ `npx graft start --watch` 运行它。需要 Node.js 22.19 或更高版本。
15
+
16
+ ## 文档
17
+
18
+ - [快速开始](https://graftland.github.io/graft/zh-cn/quickstart/)
19
+ - [命令行](https://graftland.github.io/graft/zh-cn/cli/)
20
+
21
+ 属于 [Graft](https://github.com/graftland/graft)。基于
22
+ [Apache 2.0](https://github.com/graftland/graft/blob/main/LICENSE) 许可。
@@ -0,0 +1,22 @@
1
+ # @graftland/cli
2
+
3
+ [English](https://github.com/graftland/graft/blob/main/packages/cli/README.md) |
4
+ [简体中文](https://github.com/graftland/graft/blob/main/packages/cli/README.zh-CN.md)
5
+ | 繁體中文
6
+
7
+ `graft` 命令:`start`、`create`、`add` 和 `remove`。
8
+
9
+ ```sh
10
+ npm install @graftland/cli effect
11
+ ```
12
+
13
+ 用 `npx @graftland/cli create my-app --install` 建立專案,然後用
14
+ `npx graft start --watch` 執行它。需要 Node.js 22.19 或更新版本。
15
+
16
+ ## 文件
17
+
18
+ - [快速開始](https://graftland.github.io/graft/zh-tw/quickstart/)
19
+ - [命令列](https://graftland.github.io/graft/zh-tw/cli/)
20
+
21
+ 屬於 [Graft](https://github.com/graftland/graft)。採用
22
+ [Apache 2.0](https://github.com/graftland/graft/blob/main/LICENSE) 授權。
package/dist/Manage.d.ts CHANGED
@@ -7,7 +7,7 @@ export declare const create: (options: {
7
7
  readonly name: Option.Option<string>;
8
8
  readonly install: boolean;
9
9
  }) => Effect.Effect<void, unknown, unknown>;
10
- export declare const add: (specs: ReadonlyArray<string>) => Effect.Effect<void, unknown, unknown>;
10
+ export declare const add: (specs: ReadonlyArray<string>) => Effect.Effect<void, unknown, any>;
11
11
  export declare const remove: (names: ReadonlyArray<string>) => Effect.Effect<void, unknown, unknown>;
12
12
  export { Manage_d_exports };
13
13
  //# sourceMappingURL=Manage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Manage.d.ts","names":[],"sources":["../src/Manage.ts"],"mappings":";;;;qBA8Ca,SAAM;WACT;WACA,MAAM,OAAO;WACb;MACT,OAAA;qBAaY,MAAG,OAAW,0BAAqB,OAAA;qBAGnC,SAAM,OAAW,0BAAqB,OAAA"}
1
+ {"version":3,"file":"Manage.d.ts","names":[],"sources":["../src/Manage.ts"],"mappings":";;;;qBAiDa,SAAM;WACT;WACA,MAAM,OAAO;WACb;MACT,OAAA;qBAaY,MAAG,OAAW,0BAAqB,OAAA;qBAcnC,SAAM,OAAW,0BAAqB,OAAA"}
package/dist/Manage.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { __exportAll } from "./_virtual/_rolldown/runtime.js";
2
2
  import * as Create from "@graftland/create";
3
3
  import * as PackageManager from "@graftland/package-manager";
4
- import { Array, Config, Effect, Option, Record, Schema, String } from "effect";
4
+ import { Array, Config, Effect, Option, Predicate, Record, Schema } from "effect";
5
5
  // src/Manage.ts
6
6
  /**
7
7
  * Creating projects and changing their dependencies.
@@ -12,11 +12,16 @@ var Manage_exports = /* @__PURE__ */ __exportAll({
12
12
  remove: () => remove
13
13
  });
14
14
  const report = (result) => Effect.logInfo(`${result.agent}: ${result.added.length} added, ${result.updated.length} updated, ${result.removed.length} removed`);
15
- /** Splits `name@range` (the scope's `@` excluded); a bare name means the latest version. */
16
- const parseSpec = (spec) => Option.match(Option.filter(String.lastIndexOf("@")(spec), (at) => at > 0), {
17
- onNone: () => [spec, "latest"],
18
- onSome: (at) => [String.slice(0, at)(spec), String.slice(at + 1)(spec)]
19
- });
15
+ /**
16
+ * `name@range`, split at the last `@` so a scope's `@` stays in the name. A
17
+ * bare name means the latest version.
18
+ */
19
+ const Spec = Schema.Union([Schema.TemplateLiteralParser([
20
+ PackageManager.PackageName,
21
+ "@",
22
+ Schema.String
23
+ ]), PackageManager.PackageName]);
24
+ const decodeSpecs = Schema.decodeEffect(Schema.Array(Spec));
20
25
  const install = (directory, changes) => Effect.gen(function* () {
21
26
  const decoded = yield* Schema.decodeEffect(PackageManager.Changes)(changes);
22
27
  const userAgent = yield* Config.String("npm_config_user_agent").pipe(Config.withDefault(""));
@@ -37,7 +42,7 @@ const create = (options) => Effect.gen(function* () {
37
42
  yield* Effect.logInfo(`Created ${project.name} in ${project.directory}`);
38
43
  if (options.install) yield* install(project.directory, {});
39
44
  });
40
- const add = (specs) => install(".", Record.fromEntries(Array.map(specs, parseSpec)));
45
+ const add = (specs) => Effect.flatMap(decodeSpecs(specs), (specs) => install(".", Record.fromEntries(Array.map(specs, (spec) => Predicate.isString(spec) ? [spec, "latest"] : [spec[0], spec[2]]))));
41
46
  const remove = (names) => install(".", Record.fromEntries(Array.map(names, (name) => [name, null])));
42
47
  export { Manage_exports, add, create, remove };
43
48
 
@@ -1 +1 @@
1
- {"version":3,"file":"Manage.js","names":["Str","Arr"],"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 as Arr,\n\tConfig,\n\tEffect,\n\tOption,\n\tRecord,\n\tSchema,\n\tString as Str,\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)\n\n/** Splits `name@range` (the scope's `@` excluded); a bare name means the latest version. */\nconst parseSpec = (spec: string): readonly [string, string] =>\n\tOption.match(\n\t\tOption.filter(Str.lastIndexOf(\"@\")(spec), (at) => at > 0),\n\t\t{\n\t\t\tonNone: () => [spec, \"latest\"] as const,\n\t\t\tonSome: (at) =>\n\t\t\t\t[Str.slice(0, at)(spec), Str.slice(at + 1)(spec)] as const,\n\t\t},\n\t)\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\tinstall(\".\", Record.fromEntries(Arr.map(specs, parseSpec)))\n\nexport const remove = (names: ReadonlyArray<string>) =>\n\tinstall(\n\t\t\".\",\n\t\tRecord.fromEntries(Arr.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;;AAGD,MAAM,aAAa,SAClB,OAAO,MACN,OAAO,OAAOA,OAAI,YAAY,GAAG,CAAC,CAAC,IAAI,IAAI,OAAO,KAAK,CAAC,GACxD;CACC,cAAc,CAAC,MAAM,QAAQ;CAC7B,SAAS,OACR,CAACA,OAAI,MAAM,GAAG,EAAE,CAAC,CAAC,IAAI,GAAGA,OAAI,MAAM,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAClD,CACD;AAED,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,QAAQ,KAAK,OAAO,YAAYC,MAAI,IAAI,OAAO,SAAS,CAAC,CAAC;AAE3D,MAAa,UAAU,UACtB,QACC,KACA,OAAO,YAAYA,MAAI,IAAI,QAAQ,SAAS,CAAC,MAAM,IAAI,CAAU,CAAC,CACnE"}
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)\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;;;;;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"}
package/dist/Start.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { Effect, PlatformError } from "effect";
1
+ import { Effect, PlatformError, Schema } from "effect";
2
2
  import { Graft } from "@graftland/graft";
3
+ import { Loader, Tree } from "@graftland/loader";
3
4
  declare namespace Start_d_exports {
4
5
  export { Options, run };
5
6
  }
@@ -8,12 +9,17 @@ export interface Options {
8
9
  readonly config: string;
9
10
  /** Reload plugin modules and the config file when they change. */
10
11
  readonly watch: boolean;
12
+ /**
13
+ * A `.env` file that `!env` reads when the process environment does not set
14
+ * a variable. With `watch`, changes to it are read again.
15
+ */
16
+ readonly envFile?: string | undefined;
11
17
  }
12
18
  /**
13
19
  * Loads the entries of the config file and runs until interrupted. Plugin
14
20
  * modules are imported through the hot reloader, so bare specifiers resolve
15
21
  * from the application directory whether or not `watch` is on.
16
22
  */
17
- export declare const run: (options: Options) => Effect.Effect<never, Graft.ServiceConflict | PlatformError.BadArgument>;
23
+ export declare const run: (options: Options) => Effect.Effect<never, Graft.ServiceConflict | PlatformError.BadArgument | PlatformError.PlatformError | Schema.SchemaError | Loader.StoreError | Tree.InvalidOperation>;
18
24
  export { Start_d_exports };
19
25
  //# sourceMappingURL=Start.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Start.d.ts","names":[],"sources":["../src/Start.ts"],"mappings":";;;;;iBAoBiB;;WAEP;;WAEA;;;;;;;qBAsBG,MAAG,SACN,YACP,OAAO,cAAc,MAAM,kBAAkB,cAAc"}
1
+ {"version":3,"file":"Start.d.ts","names":[],"sources":["../src/Start.ts"],"mappings":";;;;;;iBAaiB;;WAEP;;WAEA;;;;;WAKA;;;;;;;qBAQG,MAAG,SACN,YACP,OAAO,cAEP,MAAM,kBACN,cAAc,cACd,cAAc,gBACd,OAAO,cACP,OAAO,aACP,KAAK"}
package/dist/Start.js CHANGED
@@ -1,5 +1,8 @@
1
1
  import { __exportAll } from "./_virtual/_rolldown/runtime.js";
2
- import { Array, Effect, FileSystem, Layer, Logger, Path, Queue, Stream, String } from "effect";
2
+ import { EnvFile, layer, watchEnvFile } from "./internal/env.js";
3
+ import { layer as layer$1 } from "./internal/intercepts.js";
4
+ import { watchModules } from "./internal/watch.js";
5
+ import { Effect, FileSystem, Layer, Logger, Path } from "effect";
3
6
  import { NodeServices } from "@effect/platform-node";
4
7
  import { Graft, Plugin } from "@graftland/graft";
5
8
  import * as Hmr from "@graftland/hmr";
@@ -9,24 +12,12 @@ import { Include, Loader, ModuleResolver } from "@graftland/loader";
9
12
  * Runs a Graft application from its config file.
10
13
  */
11
14
  var Start_exports = /* @__PURE__ */ __exportAll({ run: () => run });
12
- const moduleExtensions = [
13
- ".js",
14
- ".mjs",
15
- ".cjs",
16
- ".ts",
17
- ".mts",
18
- ".cts"
19
- ];
20
- /** Whether a change to this file should reload plugin modules. */
21
- const isModule = (path) => !String.includes("node_modules")(path) && Array.some(moduleExtensions, (extension) => String.endsWith(extension)(path));
22
- const describe = (result) => result._tag === "Reloaded" ? Effect.logInfo(`Reloaded ${result.entries.length} plugin module(s)`) : Effect.logWarning(`Restart required to pick up ${Array.join(result.modules, ", ")}`);
23
15
  /**
24
16
  * Loads the entries of the config file and runs until interrupted. Plugin
25
17
  * modules are imported through the hot reloader, so bare specifiers resolve
26
18
  * from the application directory whether or not `watch` is on.
27
19
  */
28
20
  const run = (options) => Effect.gen(function* () {
29
- const fs = yield* FileSystem.FileSystem;
30
21
  const path = yield* Path.Path;
31
22
  const file = path.resolve(options.config);
32
23
  const directory = path.dirname(file);
@@ -34,29 +25,34 @@ const run = (options) => Effect.gen(function* () {
34
25
  const hmr = yield* Hmr.make({ base: base.href });
35
26
  const resolver = yield* ModuleResolver.make({
36
27
  base: directory,
37
- importer: hmr.importer
28
+ importer: hmr.importerScoped
38
29
  });
39
30
  yield* Graft.provide(ModuleResolver.ModuleResolver, resolver);
40
- yield* Graft.plugin(Include.Include, {
31
+ yield* (yield* Graft.plugin(Include.Include, {
41
32
  path: file,
42
33
  watch: options.watch
43
- });
34
+ })).await;
44
35
  if (options.watch) {
45
- const changed = yield* Queue.sliding(1);
46
- yield* fs.watch(directory, { recursive: true }).pipe(Stream.filter((event) => isModule(event.path)), Stream.runForEach(() => Queue.offer(changed, void 0)), Effect.forkScoped);
36
+ const { reload } = yield* EnvFile;
37
+ const envFile = options.envFile;
47
38
  const reloader = Plugin.make({
48
39
  name: "reloader",
49
- inject: [Loader.Loader],
40
+ inject: [
41
+ Loader.Loader,
42
+ FileSystem.FileSystem,
43
+ Path.Path
44
+ ],
50
45
  apply: () => Effect.gen(function* () {
51
46
  const loader = yield* Loader.Loader;
52
- yield* Queue.take(changed).pipe(Effect.andThen(Effect.sleep("50 millis")), Effect.andThen(Queue.clear(changed)), Effect.andThen(hmr.reload), Effect.flatMap(describe), Effect.andThen(loader.retry), Effect.catchCause((cause) => Effect.logError("Reloading plugin modules failed", cause)), Effect.forever, Effect.forkScoped);
47
+ yield* watchModules(hmr, directory, loader.retry).pipe(Effect.forkScoped);
48
+ if (envFile !== void 0) yield* watchEnvFile(envFile, reload, loader.retry).pipe(Effect.forkScoped);
53
49
  })
54
50
  });
55
51
  yield* Graft.plugin(reloader);
56
52
  }
57
53
  yield* Effect.logInfo(`Running ${path.relative(process.cwd(), file) || file}`);
58
54
  return yield* Effect.never;
59
- }).pipe(Effect.scoped, Effect.provide(Layer.provideMerge(Graft.layer, Layer.mergeAll(NodeServices.layer, Logger.layer([Logger.consolePretty()])))));
55
+ }).pipe(Effect.scoped, Effect.provide(Layer.provideMerge(Graft.layer, Layer.mergeAll(NodeServices.layer, layer$1, layer(options.envFile).pipe(Layer.provide(NodeServices.layer)), Logger.layer([Logger.consolePretty()])))));
60
56
  export { Start_exports, run };
61
57
 
62
58
  //# sourceMappingURL=Start.js.map
package/dist/Start.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Start.js","names":["Str","Arr"],"sources":["../src/Start.ts"],"sourcesContent":["/**\n * Runs a Graft application from its config file.\n */\nimport { NodeServices } from \"@effect/platform-node\"\nimport { Graft, Plugin } from \"@graftland/graft\"\nimport * as Hmr from \"@graftland/hmr\"\nimport { Include, Loader, ModuleResolver } from \"@graftland/loader\"\nimport type { PlatformError } from \"effect\"\nimport {\n\tArray as Arr,\n\tEffect,\n\tFileSystem,\n\tLayer,\n\tLogger,\n\tPath,\n\tQueue,\n\tStream,\n\tString as Str,\n} from \"effect\"\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}\n\nconst moduleExtensions = [\".js\", \".mjs\", \".cjs\", \".ts\", \".mts\", \".cts\"]\n\n/** Whether a change to this file should reload plugin modules. */\nconst isModule = (path: string) =>\n\t!Str.includes(\"node_modules\")(path) &&\n\tArr.some(moduleExtensions, (extension) => Str.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\t`Restart required to pick up ${Arr.join(result.modules, \", \")}`,\n\t\t\t)\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<never, Graft.ServiceConflict | PlatformError.BadArgument> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem\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.importer,\n\t\t})\n\t\tyield* Graft.provide(ModuleResolver.ModuleResolver, resolver)\n\t\tyield* Graft.plugin(Include.Include, { path: file, watch: options.watch })\n\t\tif (options.watch) {\n\t\t\t// A save raises several events; a one-slot sliding queue folds them into one pending reload.\n\t\t\tconst changed = yield* Queue.sliding<void>(1)\n\t\t\t// Only module changes reload code; the config file is watched by the loader itself.\n\t\t\tyield* fs.watch(directory, { recursive: true }).pipe(\n\t\t\t\tStream.filter((event) => isModule(event.path)),\n\t\t\t\tStream.runForEach(() => Queue.offer(changed, undefined)),\n\t\t\t\tEffect.forkScoped,\n\t\t\t)\n\t\t\tconst reloader = Plugin.make({\n\t\t\t\tname: \"reloader\",\n\t\t\t\tinject: [Loader.Loader],\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* Queue.take(changed).pipe(\n\t\t\t\t\t\t\tEffect.andThen(Effect.sleep(\"50 millis\")),\n\t\t\t\t\t\t\tEffect.andThen(Queue.clear(changed)),\n\t\t\t\t\t\t\tEffect.andThen(hmr.reload),\n\t\t\t\t\t\t\tEffect.flatMap(describe),\n\t\t\t\t\t\t\t// Entries whose module failed to load get another chance with the new code.\n\t\t\t\t\t\t\tEffect.andThen(loader.retry),\n\t\t\t\t\t\t\tEffect.catchCause((cause) =>\n\t\t\t\t\t\t\t\tEffect.logError(\"Reloading plugin modules failed\", cause),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tEffect.forever,\n\t\t\t\t\t\t\tEffect.forkScoped,\n\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\tNodeServices.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":";;;;;;;;;;;AA2BA,MAAM,mBAAmB;CAAC;CAAO;CAAQ;CAAQ;CAAO;CAAQ;AAAM;;AAGtE,MAAM,YAAY,SACjB,CAACA,OAAI,SAAS,cAAc,CAAC,CAAC,IAAI,KAClCC,MAAI,KAAK,mBAAmB,cAAcD,OAAI,SAAS,SAAS,CAAC,CAAC,IAAI,CAAC;AAExE,MAAM,YAAY,WACjB,OAAO,SAAS,aACb,OAAO,QAAQ,YAAY,OAAO,QAAQ,OAAO,kBAAkB,IACnE,OAAO,WACP,+BAA+BC,MAAI,KAAK,OAAO,SAAS,IAAI,GAC7D;;;;;;AAOH,MAAa,OACZ,YAEA,OAAO,IAAI,aAAa;CACvB,MAAM,KAAK,OAAO,WAAW;CAC7B,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,OAAO,MAAM,QAAQ,eAAe,gBAAgB,QAAQ;CAC5D,OAAO,MAAM,OAAO,QAAQ,SAAS;EAAE,MAAM;EAAM,OAAO,QAAQ;CAAM,CAAC;CACzE,IAAI,QAAQ,OAAO;EAElB,MAAM,UAAU,OAAO,MAAM,QAAc,CAAC;EAE5C,OAAO,GAAG,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC,CAAC,CAAC,KAC/C,OAAO,QAAQ,UAAU,SAAS,MAAM,IAAI,CAAC,GAC7C,OAAO,iBAAiB,MAAM,MAAM,SAAS,KAAA,CAAS,CAAC,GACvD,OAAO,UACR;EACA,MAAM,WAAW,OAAO,KAAK;GAC5B,MAAM;GACN,QAAQ,CAAC,OAAO,MAAM;GACtB,aACC,OAAO,IAAI,aAAa;IACvB,MAAM,SAAS,OAAO,OAAO;IAC7B,OAAO,MAAM,KAAK,OAAO,CAAC,CAAC,KAC1B,OAAO,QAAQ,OAAO,MAAM,WAAW,CAAC,GACxC,OAAO,QAAQ,MAAM,MAAM,OAAO,CAAC,GACnC,OAAO,QAAQ,IAAI,MAAM,GACzB,OAAO,QAAQ,QAAQ,GAEvB,OAAO,QAAQ,OAAO,KAAK,GAC3B,OAAO,YAAY,UAClB,OAAO,SAAS,mCAAmC,KAAK,CACzD,GACA,OAAO,SACP,OAAO,UACR;GACD,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,OACb,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 { 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\tyield* Graft.provide(ModuleResolver.ModuleResolver, resolver)\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\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":";;;;;;;;;;;;;;;;;;;AA8BA,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,OAAO,MAAM,QAAQ,eAAe,gBAAgB,QAAQ;CAK5D,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,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"}
package/dist/bin.js CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ import { version } from "./package.js";
2
3
  import { add as add$1, create as create$1, remove as remove$1 } from "./Manage.js";
3
4
  import { run } from "./Start.js";
4
- import { Effect, FileSystem, Path, Schema } from "effect";
5
+ import { Effect, Option } from "effect";
5
6
  import { NodeRuntime, NodeServices } from "@effect/platform-node";
6
7
  import { Argument, Command, Flag } from "effect/cli";
7
8
  // src/bin.ts
@@ -10,8 +11,12 @@ import { Argument, Command, Flag } from "effect/cli";
10
11
  */
11
12
  const start = Command.make("start", {
12
13
  config: Argument.String("config").pipe(Argument.withDescription("Path of the config file"), Argument.withDefault("graft.yml")),
13
- watch: Flag.Boolean("watch").pipe(Flag.withAlias("w"), Flag.withDescription("Reload plugins and config when files change"), Flag.withDefault(false))
14
- }, run).pipe(Command.withDescription("Run the application described by a config file"));
14
+ watch: Flag.Boolean("watch").pipe(Flag.withAlias("w"), Flag.withDescription("Reload plugins and config when files change"), Flag.withDefault(false)),
15
+ envFile: Flag.String("env-file").pipe(Flag.withDescription("Read variables for !env from a .env file; the process environment takes precedence"), Flag.optional)
16
+ }, (options) => run({
17
+ ...options,
18
+ envFile: Option.getOrUndefined(options.envFile)
19
+ })).pipe(Command.withDescription("Run the application described by a config file"));
15
20
  const create = Command.make("create", {
16
21
  directory: Argument.String("directory").pipe(Argument.withDescription("Where to create the project")),
17
22
  name: Flag.String("name").pipe(Flag.withDescription("Package name; defaults to the directory name"), Flag.optional),
@@ -25,14 +30,7 @@ const graft = Command.make("graft").pipe(Command.withDescription("Run and manage
25
30
  add,
26
31
  remove
27
32
  ]));
28
- const Manifest = Schema.fromJsonString(Schema.Struct({ version: Schema.String }));
29
- Effect.gen(function* () {
30
- const fs = yield* FileSystem.FileSystem;
31
- const file = yield* (yield* Path.Path).fromFileUrl(new URL("../package.json", import.meta.url));
32
- const text = yield* fs.readFileString(file);
33
- const { version } = yield* Schema.decodeEffect(Manifest)(text);
34
- return yield* Command.run(graft, { version });
35
- }).pipe(Effect.provide(NodeServices.layer), NodeRuntime.runMain);
33
+ Command.run(graft, { version }).pipe(Effect.provide(NodeServices.layer), NodeRuntime.runMain);
36
34
  export {};
37
35
 
38
36
  //# sourceMappingURL=bin.js.map
package/dist/bin.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"bin.js","names":["Start.run","Manage.create","Manage.add","Manage.remove"],"sources":["../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * The `graft` executable. Running this module starts the program.\n */\nimport { NodeRuntime, NodeServices } from \"@effect/platform-node\"\nimport { Effect, FileSystem, Path, Schema } from \"effect\"\nimport { Argument, Command, Flag } from \"effect/cli\"\nimport * as Manage from \"./Manage\"\nimport * as Start from \"./Start\"\n\nconst start = Command.make(\n\t\"start\",\n\t{\n\t\tconfig: Argument.String(\"config\").pipe(\n\t\t\tArgument.withDescription(\"Path of the config file\"),\n\t\t\tArgument.withDefault(\"graft.yml\"),\n\t\t),\n\t\twatch: Flag.Boolean(\"watch\").pipe(\n\t\t\tFlag.withAlias(\"w\"),\n\t\t\tFlag.withDescription(\"Reload plugins and config when files change\"),\n\t\t\tFlag.withDefault(false),\n\t\t),\n\t},\n\tStart.run,\n).pipe(\n\tCommand.withDescription(\"Run the application described by a config file\"),\n)\n\nconst create = Command.make(\n\t\"create\",\n\t{\n\t\tdirectory: Argument.String(\"directory\").pipe(\n\t\t\tArgument.withDescription(\"Where to create the project\"),\n\t\t),\n\t\tname: Flag.String(\"name\").pipe(\n\t\t\tFlag.withDescription(\"Package name; defaults to the directory name\"),\n\t\t\tFlag.optional,\n\t\t),\n\t\tinstall: Flag.Boolean(\"install\").pipe(\n\t\t\tFlag.withDescription(\"Install dependencies afterwards\"),\n\t\t\tFlag.withDefault(false),\n\t\t),\n\t},\n\tManage.create,\n).pipe(Command.withDescription(\"Create a new Graft project\"))\n\nconst add = Command.make(\n\t\"add\",\n\t{ packages: Argument.variadic(Argument.String(\"package\")) },\n\t({ packages }) => Manage.add(packages),\n).pipe(\n\tCommand.withDescription(\n\t\t\"Add dependencies, restoring the project if installing fails\",\n\t),\n)\n\nconst remove = Command.make(\n\t\"remove\",\n\t{ packages: Argument.variadic(Argument.String(\"package\")) },\n\t({ packages }) => Manage.remove(packages),\n).pipe(\n\tCommand.withDescription(\n\t\t\"Remove dependencies, restoring the project if installing fails\",\n\t),\n)\n\nconst graft = Command.make(\"graft\").pipe(\n\tCommand.withDescription(\"Run and manage Graft applications\"),\n\tCommand.withSubcommands([start, create, add, remove]),\n)\n\nconst Manifest = Schema.fromJsonString(\n\tSchema.Struct({ version: Schema.String }),\n)\n\nEffect.gen(function* () {\n\tconst fs = yield* FileSystem.FileSystem\n\tconst path = yield* Path.Path\n\tconst file = yield* path.fromFileUrl(\n\t\tnew URL(\"../package.json\", import.meta.url),\n\t)\n\tconst text = yield* fs.readFileString(file)\n\tconst { version } = yield* Schema.decodeEffect(Manifest)(text)\n\treturn yield* Command.run(graft, { version })\n}).pipe(Effect.provide(NodeServices.layer), NodeRuntime.runMain)\n"],"mappings":";;;;;;;;;;AAUA,MAAM,QAAQ,QAAQ,KACrB,SACA;CACC,QAAQ,SAAS,OAAO,QAAQ,CAAC,CAAC,KACjC,SAAS,gBAAgB,yBAAyB,GAClD,SAAS,YAAY,WAAW,CACjC;CACA,OAAO,KAAK,QAAQ,OAAO,CAAC,CAAC,KAC5B,KAAK,UAAU,GAAG,GAClB,KAAK,gBAAgB,6CAA6C,GAClE,KAAK,YAAY,KAAK,CACvB;AACD,GACAA,GACD,CAAC,CAAC,KACD,QAAQ,gBAAgB,gDAAgD,CACzE;AAEA,MAAM,SAAS,QAAQ,KACtB,UACA;CACC,WAAW,SAAS,OAAO,WAAW,CAAC,CAAC,KACvC,SAAS,gBAAgB,6BAA6B,CACvD;CACA,MAAM,KAAK,OAAO,MAAM,CAAC,CAAC,KACzB,KAAK,gBAAgB,8CAA8C,GACnE,KAAK,QACN;CACA,SAAS,KAAK,QAAQ,SAAS,CAAC,CAAC,KAChC,KAAK,gBAAgB,iCAAiC,GACtD,KAAK,YAAY,KAAK,CACvB;AACD,GACAC,QACD,CAAC,CAAC,KAAK,QAAQ,gBAAgB,4BAA4B,CAAC;AAE5D,MAAM,MAAM,QAAQ,KACnB,OACA,EAAE,UAAU,SAAS,SAAS,SAAS,OAAO,SAAS,CAAC,EAAE,IACzD,EAAE,eAAeC,MAAW,QAAQ,CACtC,CAAC,CAAC,KACD,QAAQ,gBACP,6DACD,CACD;AAEA,MAAM,SAAS,QAAQ,KACtB,UACA,EAAE,UAAU,SAAS,SAAS,SAAS,OAAO,SAAS,CAAC,EAAE,IACzD,EAAE,eAAeC,SAAc,QAAQ,CACzC,CAAC,CAAC,KACD,QAAQ,gBACP,gEACD,CACD;AAEA,MAAM,QAAQ,QAAQ,KAAK,OAAO,CAAC,CAAC,KACnC,QAAQ,gBAAgB,mCAAmC,GAC3D,QAAQ,gBAAgB;CAAC;CAAO;CAAQ;CAAK;AAAM,CAAC,CACrD;AAEA,MAAM,WAAW,OAAO,eACvB,OAAO,OAAO,EAAE,SAAS,OAAO,OAAO,CAAC,CACzC;AAEA,OAAO,IAAI,aAAa;CACvB,MAAM,KAAK,OAAO,WAAW;CAE7B,MAAM,OAAO,QAAO,OADA,KAAK,KAAA,CACA,YACxB,IAAI,IAAI,mBAAmB,YAAY,GAAG,CAC3C;CACA,MAAM,OAAO,OAAO,GAAG,eAAe,IAAI;CAC1C,MAAM,EAAE,YAAY,OAAO,OAAO,aAAa,QAAQ,CAAC,CAAC,IAAI;CAC7D,OAAO,OAAO,QAAQ,IAAI,OAAO,EAAE,QAAQ,CAAC;AAC7C,CAAC,CAAC,CAAC,KAAK,OAAO,QAAQ,aAAa,KAAK,GAAG,YAAY,OAAO"}
1
+ {"version":3,"file":"bin.js","names":["Start.run","Manage.create","Manage.add","Manage.remove"],"sources":["../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * The `graft` executable. Running this module starts the program.\n */\nimport { NodeRuntime, NodeServices } from \"@effect/platform-node\"\nimport { Effect, Option } from \"effect\"\nimport { Argument, Command, Flag } from \"effect/cli\"\nimport { version } from \"../package.json\" with { type: \"json\" }\nimport * as Manage from \"./Manage\"\nimport * as Start from \"./Start\"\n\nconst start = Command.make(\n\t\"start\",\n\t{\n\t\tconfig: Argument.String(\"config\").pipe(\n\t\t\tArgument.withDescription(\"Path of the config file\"),\n\t\t\tArgument.withDefault(\"graft.yml\"),\n\t\t),\n\t\twatch: Flag.Boolean(\"watch\").pipe(\n\t\t\tFlag.withAlias(\"w\"),\n\t\t\tFlag.withDescription(\"Reload plugins and config when files change\"),\n\t\t\tFlag.withDefault(false),\n\t\t),\n\t\tenvFile: Flag.String(\"env-file\").pipe(\n\t\t\tFlag.withDescription(\n\t\t\t\t\"Read variables for !env from a .env file; the process environment takes precedence\",\n\t\t\t),\n\t\t\tFlag.optional,\n\t\t),\n\t},\n\t(options) =>\n\t\tStart.run({ ...options, envFile: Option.getOrUndefined(options.envFile) }),\n).pipe(\n\tCommand.withDescription(\"Run the application described by a config file\"),\n)\n\nconst create = Command.make(\n\t\"create\",\n\t{\n\t\tdirectory: Argument.String(\"directory\").pipe(\n\t\t\tArgument.withDescription(\"Where to create the project\"),\n\t\t),\n\t\tname: Flag.String(\"name\").pipe(\n\t\t\tFlag.withDescription(\"Package name; defaults to the directory name\"),\n\t\t\tFlag.optional,\n\t\t),\n\t\tinstall: Flag.Boolean(\"install\").pipe(\n\t\t\tFlag.withDescription(\"Install dependencies afterwards\"),\n\t\t\tFlag.withDefault(false),\n\t\t),\n\t},\n\tManage.create,\n).pipe(Command.withDescription(\"Create a new Graft project\"))\n\nconst add = Command.make(\n\t\"add\",\n\t{ packages: Argument.variadic(Argument.String(\"package\")) },\n\t({ packages }) => Manage.add(packages),\n).pipe(\n\tCommand.withDescription(\n\t\t\"Add dependencies, restoring the project if installing fails\",\n\t),\n)\n\nconst remove = Command.make(\n\t\"remove\",\n\t{ packages: Argument.variadic(Argument.String(\"package\")) },\n\t({ packages }) => Manage.remove(packages),\n).pipe(\n\tCommand.withDescription(\n\t\t\"Remove dependencies, restoring the project if installing fails\",\n\t),\n)\n\nconst graft = Command.make(\"graft\").pipe(\n\tCommand.withDescription(\"Run and manage Graft applications\"),\n\tCommand.withSubcommands([start, create, add, remove]),\n)\n\nCommand.run(graft, { version }).pipe(\n\tEffect.provide(NodeServices.layer),\n\tNodeRuntime.runMain,\n)\n"],"mappings":";;;;;;;;;;;AAWA,MAAM,QAAQ,QAAQ,KACrB,SACA;CACC,QAAQ,SAAS,OAAO,QAAQ,CAAC,CAAC,KACjC,SAAS,gBAAgB,yBAAyB,GAClD,SAAS,YAAY,WAAW,CACjC;CACA,OAAO,KAAK,QAAQ,OAAO,CAAC,CAAC,KAC5B,KAAK,UAAU,GAAG,GAClB,KAAK,gBAAgB,6CAA6C,GAClE,KAAK,YAAY,KAAK,CACvB;CACA,SAAS,KAAK,OAAO,UAAU,CAAC,CAAC,KAChC,KAAK,gBACJ,oFACD,GACA,KAAK,QACN;AACD,IACC,YACAA,IAAU;CAAE,GAAG;CAAS,SAAS,OAAO,eAAe,QAAQ,OAAO;AAAE,CAAC,CAC3E,CAAC,CAAC,KACD,QAAQ,gBAAgB,gDAAgD,CACzE;AAEA,MAAM,SAAS,QAAQ,KACtB,UACA;CACC,WAAW,SAAS,OAAO,WAAW,CAAC,CAAC,KACvC,SAAS,gBAAgB,6BAA6B,CACvD;CACA,MAAM,KAAK,OAAO,MAAM,CAAC,CAAC,KACzB,KAAK,gBAAgB,8CAA8C,GACnE,KAAK,QACN;CACA,SAAS,KAAK,QAAQ,SAAS,CAAC,CAAC,KAChC,KAAK,gBAAgB,iCAAiC,GACtD,KAAK,YAAY,KAAK,CACvB;AACD,GACAC,QACD,CAAC,CAAC,KAAK,QAAQ,gBAAgB,4BAA4B,CAAC;AAE5D,MAAM,MAAM,QAAQ,KACnB,OACA,EAAE,UAAU,SAAS,SAAS,SAAS,OAAO,SAAS,CAAC,EAAE,IACzD,EAAE,eAAeC,MAAW,QAAQ,CACtC,CAAC,CAAC,KACD,QAAQ,gBACP,6DACD,CACD;AAEA,MAAM,SAAS,QAAQ,KACtB,UACA,EAAE,UAAU,SAAS,SAAS,SAAS,OAAO,SAAS,CAAC,EAAE,IACzD,EAAE,eAAeC,SAAc,QAAQ,CACzC,CAAC,CAAC,KACD,QAAQ,gBACP,gEACD,CACD;AAEA,MAAM,QAAQ,QAAQ,KAAK,OAAO,CAAC,CAAC,KACnC,QAAQ,gBAAgB,mCAAmC,GAC3D,QAAQ,gBAAgB;CAAC;CAAO;CAAQ;CAAK;AAAM,CAAC,CACrD;AAEA,QAAQ,IAAI,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,KAC/B,OAAO,QAAQ,aAAa,KAAK,GACjC,YAAY,OACb"}
@@ -0,0 +1,7 @@
1
+ import { JsonSchema } from "effect";
2
+ /** Draft-07, which YAML language servers support most widely. */
3
+ export declare const make: () => {
4
+ $schema: string;
5
+ definitions: JsonSchema.Definitions;
6
+ };
7
+ //# sourceMappingURL=configSchema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configSchema.d.ts","names":[],"sources":["../../src/internal/configSchema.ts"],"mappings":";;qBA0Ea"}
@@ -0,0 +1,44 @@
1
+ import { registrations } from "./intercepts.js";
2
+ import { Array, JsonSchema, Record, Schema } from "effect";
3
+ import { Entry } from "@graftland/loader";
4
+ // src/internal/configSchema.ts
5
+ /**
6
+ * The JSON Schema of the config file that graft start reads, derived from the
7
+ * loader's entry fields and the intercepts the CLI registers. The build writes
8
+ * it to the package's schema.json, so each release ships its own version.
9
+ */
10
+ const fields = Entry.Entry.schema.fields;
11
+ const Intercept$1 = Schema.Struct(Record.fromEntries(Array.map(registrations, ({ key, schema }) => [key, Schema.optionalKey(schema)]))).annotate({
12
+ identifier: "Intercept",
13
+ description: "Patches for references, applying to this entry and its descendants."
14
+ });
15
+ const Metadata = [Schema.Record(Schema.String, Schema.Unknown)];
16
+ const shared = {
17
+ ...fields,
18
+ id: Schema.optionalKey(fields.id.annotate({ description: "Identifies the entry. Graft adds one, and writes it to the file, if an entry has none." })),
19
+ intercept: Schema.optionalKey(Intercept$1)
20
+ };
21
+ const FileEntry = Schema.Union([Schema.StructWithRest(Schema.Struct({
22
+ ...shared,
23
+ group: Schema.Literal(true).annotate({ description: "true makes the entry a group of other entries." }),
24
+ config: Schema.optionalKey(Schema.Array(Schema.suspend(() => FileEntry)).annotate({ description: "The entries of this group." }))
25
+ }), Metadata), Schema.StructWithRest(Schema.Struct({
26
+ ...shared,
27
+ group: Schema.optionalKey(Schema.Literal(false).annotate({ description: "true makes the entry a group of other entries." }))
28
+ }), Metadata)]).annotate({ identifier: "Entry" });
29
+ const File = Schema.NullOr(Schema.Array(FileEntry)).annotate({
30
+ title: "Graft config file",
31
+ description: "The entries that graft start loads. Each entry loads one plugin, or groups other entries."
32
+ });
33
+ /** Draft-07, which YAML language servers support most widely. */
34
+ const make = () => {
35
+ const document = JsonSchema.toDocumentDraft07(Schema.toJsonSchemaDocument(File, { onExcessProperty: "error" }));
36
+ return {
37
+ $schema: JsonSchema.META_SCHEMA_URI_DRAFT_07,
38
+ ...document.schema,
39
+ definitions: document.definitions
40
+ };
41
+ };
42
+ export { make };
43
+
44
+ //# sourceMappingURL=configSchema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configSchema.js","names":["Intercept"],"sources":["../../src/internal/configSchema.ts"],"sourcesContent":["/**\n * The JSON Schema of the config file that graft start reads, derived from the\n * loader's entry fields and the intercepts the CLI registers. The build writes\n * it to the package's schema.json, so each release ships its own version.\n */\nimport { Entry } from \"@graftland/loader\"\nimport { Array, JsonSchema, Record, Schema } from \"effect\"\nimport { registrations } from \"./intercepts\"\n\nconst fields = Entry.Entry.schema.fields\n\nconst Intercept = Schema.Struct(\n\tRecord.fromEntries(\n\t\tArray.map(\n\t\t\tregistrations,\n\t\t\t({ key, schema }) => [key, Schema.optionalKey(schema)] as const,\n\t\t),\n\t),\n).annotate({\n\tidentifier: \"Intercept\",\n\tdescription:\n\t\t\"Patches for references, applying to this entry and its descendants.\",\n})\n\nconst Metadata = [Schema.Record(Schema.String, Schema.Unknown)] as const\n\nconst shared = {\n\t...fields,\n\tid: Schema.optionalKey(\n\t\tfields.id.annotate({\n\t\t\tdescription:\n\t\t\t\t\"Identifies the entry. Graft adds one, and writes it to the file, if an entry has none.\",\n\t\t}),\n\t),\n\tintercept: Schema.optionalKey(Intercept),\n}\n\n// Two shapes instead of one, so that only a group's config is checked as a\n// list of entries; a plugin's config is checked by the plugin's own Schema.\nconst FileEntry: Schema.Top = Schema.Union([\n\tSchema.StructWithRest(\n\t\tSchema.Struct({\n\t\t\t...shared,\n\t\t\tgroup: Schema.Literal(true).annotate({\n\t\t\t\tdescription: \"true makes the entry a group of other entries.\",\n\t\t\t}),\n\t\t\tconfig: Schema.optionalKey(\n\t\t\t\tSchema.Array(Schema.suspend(() => FileEntry)).annotate({\n\t\t\t\t\tdescription: \"The entries of this group.\",\n\t\t\t\t}),\n\t\t\t),\n\t\t}),\n\t\tMetadata,\n\t),\n\tSchema.StructWithRest(\n\t\tSchema.Struct({\n\t\t\t...shared,\n\t\t\tgroup: Schema.optionalKey(\n\t\t\t\tSchema.Literal(false).annotate({\n\t\t\t\t\tdescription: \"true makes the entry a group of other entries.\",\n\t\t\t\t}),\n\t\t\t),\n\t\t}),\n\t\tMetadata,\n\t),\n]).annotate({ identifier: \"Entry\" })\n\nconst File = Schema.NullOr(Schema.Array(FileEntry)).annotate({\n\ttitle: \"Graft config file\",\n\tdescription:\n\t\t\"The entries that graft start loads. Each entry loads one plugin, or groups other entries.\",\n})\n\n/** Draft-07, which YAML language servers support most widely. */\nexport const make = () => {\n\tconst document = JsonSchema.toDocumentDraft07(\n\t\t// Only the intercept struct is closed: entries keep a rest record for metadata.\n\t\tSchema.toJsonSchemaDocument(File, { onExcessProperty: \"error\" }),\n\t)\n\treturn {\n\t\t$schema: JsonSchema.META_SCHEMA_URI_DRAFT_07,\n\t\t...document.schema,\n\t\tdefinitions: document.definitions,\n\t}\n}\n"],"mappings":";;;;;;;;;AASA,MAAM,SAAS,MAAM,MAAM,OAAO;AAElC,MAAMA,cAAY,OAAO,OACxB,OAAO,YACN,MAAM,IACL,gBACC,EAAE,KAAK,aAAa,CAAC,KAAK,OAAO,YAAY,MAAM,CAAC,CACtD,CACD,CACD,CAAC,CAAC,SAAS;CACV,YAAY;CACZ,aACC;AACF,CAAC;AAED,MAAM,WAAW,CAAC,OAAO,OAAO,OAAO,QAAQ,OAAO,OAAO,CAAC;AAE9D,MAAM,SAAS;CACd,GAAG;CACH,IAAI,OAAO,YACV,OAAO,GAAG,SAAS,EAClB,aACC,yFACF,CAAC,CACF;CACA,WAAW,OAAO,YAAYA,WAAS;AACxC;AAIA,MAAM,YAAwB,OAAO,MAAM,CAC1C,OAAO,eACN,OAAO,OAAO;CACb,GAAG;CACH,OAAO,OAAO,QAAQ,IAAI,CAAC,CAAC,SAAS,EACpC,aAAa,iDACd,CAAC;CACD,QAAQ,OAAO,YACd,OAAO,MAAM,OAAO,cAAc,SAAS,CAAC,CAAC,CAAC,SAAS,EACtD,aAAa,6BACd,CAAC,CACF;AACD,CAAC,GACD,QACD,GACA,OAAO,eACN,OAAO,OAAO;CACb,GAAG;CACH,OAAO,OAAO,YACb,OAAO,QAAQ,KAAK,CAAC,CAAC,SAAS,EAC9B,aAAa,iDACd,CAAC,CACF;AACD,CAAC,GACD,QACD,CACD,CAAC,CAAC,CAAC,SAAS,EAAE,YAAY,QAAQ,CAAC;AAEnC,MAAM,OAAO,OAAO,OAAO,OAAO,MAAM,SAAS,CAAC,CAAC,CAAC,SAAS;CAC5D,OAAO;CACP,aACC;AACF,CAAC;;AAGD,MAAa,aAAa;CACzB,MAAM,WAAW,WAAW,kBAE3B,OAAO,qBAAqB,MAAM,EAAE,kBAAkB,QAAQ,CAAC,CAChE;CACA,OAAO;EACN,SAAS,WAAW;EACpB,GAAG,SAAS;EACZ,aAAa,SAAS;CACvB;AACD"}
@@ -0,0 +1,50 @@
1
+ import { Cause, ConfigProvider, Context, Deferred, Effect, Exit, FileSystem, Layer, Path, Queue, Ref, Stream } from "effect";
2
+ // src/internal/env.ts
3
+ const options = { preserveEmptyStrings: true };
4
+ var EnvFile = class extends Context.Service()("@graftland/cli/internal/env/EnvFile") {};
5
+ /** Provides `ConfigProvider` and `EnvFile`; a missing file fails the layer. */
6
+ const layer = (file) => Layer.unwrap(Effect.gen(function* () {
7
+ const environment = ConfigProvider.fromEnv(options);
8
+ if (file === void 0) return Layer.merge(ConfigProvider.layer(environment), Layer.succeed(EnvFile, { reload: Effect.succeed(false) }));
9
+ const fs = yield* FileSystem.FileSystem;
10
+ const read = Effect.gen(function* () {
11
+ const target = yield* fs.realPath(file);
12
+ const text = yield* fs.readFileString(target);
13
+ return {
14
+ target,
15
+ text,
16
+ provider: ConfigProvider.fromDotEnvContents(text, options)
17
+ };
18
+ });
19
+ const initial = yield* read;
20
+ const loaded = yield* Ref.make(initial);
21
+ const fromFile = ConfigProvider.make((path) => Effect.flatMap(Ref.get(loaded), ({ provider }) => provider.load(path)));
22
+ return Layer.merge(ConfigProvider.layer(ConfigProvider.orElse(environment, fromFile)), Layer.succeed(EnvFile, { reload: Effect.gen(function* () {
23
+ const next = yield* read;
24
+ const previous = yield* Ref.getAndSet(loaded, next);
25
+ return next.text !== previous.text;
26
+ }) }));
27
+ }));
28
+ /**
29
+ * The handlers below let interruption through, and with it any failure raised
30
+ * while stopping. Log those; failures without interruption are handled there.
31
+ */
32
+ const logStopped = (file) => (exit) => Exit.isFailure(exit) && Cause.hasInterrupts(exit.cause) && (Cause.hasFails(exit.cause) || Cause.hasDies(exit.cause)) ? Effect.logError(`Watching ${file} stopped with a failure`, exit.cause) : Effect.void;
33
+ /**
34
+ * Reloads the file when it changes and retries the loader's entries, which
35
+ * reads `!env` again. A file that cannot be read keeps its previous values.
36
+ */
37
+ const watchEnvFile = (file, reload, retry) => Effect.gen(function* () {
38
+ const fs = yield* FileSystem.FileSystem;
39
+ const path = yield* Path.Path;
40
+ const ready = yield* Deferred.make();
41
+ const events = Stream.callback((queue) => fs.watch(path.dirname(file), { onReady: () => Deferred.doneUnsafe(ready, Effect.void) }).pipe(Stream.filter((event) => path.basename(event.path) === path.basename(file)), Stream.runForEachArray((events) => Queue.offerAll(queue, events)), Effect.scoped, Effect.onExit(logStopped(file)), Effect.catchCauseIf((cause) => !Cause.hasInterrupts(cause), (cause) => Effect.logError(`Watching ${file} failed`, cause)), Effect.andThen(Effect.sleep("1 second")), Effect.forever), { bufferSize: 16 });
42
+ yield* Stream.mergeAll([
43
+ Stream.as(events, void 0),
44
+ Stream.fromEffect(Deferred.await(ready)),
45
+ Stream.tick("1 second").pipe(Stream.drop(1))
46
+ ], { 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)));
47
+ }).pipe(Effect.onExit(logStopped(file)));
48
+ export { EnvFile, layer, watchEnvFile };
49
+
50
+ //# sourceMappingURL=env.js.map
@@ -0,0 +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 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\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":";;AAsBA,MAAM,UAAU,EAAE,sBAAsB,KAAK;AAQ7C,IAAa,UAAb,cAA6B,QAAQ,QAMnC,CAAC,CAAC,qCAAqC,CAAC,CAAC,CAAC;;AAG5C,MAAa,SAAS,SACrB,MAAM,OACL,OAAO,IAAI,aAAa;CACvB,MAAM,cAAc,eAAe,QAAQ,OAAO;CAClD,IAAI,SAAS,KAAA,GACZ,OAAO,MAAM,MACZ,eAAe,MAAM,WAAW,GAChC,MAAM,QAAQ,SAAS,EAAE,QAAQ,OAAO,QAAQ,KAAK,EAAE,CAAC,CACzD;CACD,MAAM,KAAK,OAAO,WAAW;CAI7B,MAAM,OAAO,OAAO,IAAI,aAAa;EACpC,MAAM,SAAS,OAAO,GAAG,SAAS,IAAI;EACtC,MAAM,OAAO,OAAO,GAAG,eAAe,MAAM;EAC5C,OAAO;GACN;GACA;GACA,UAAU,eAAe,mBAAmB,MAAM,OAAO;EAC1D;CACD,CAAC;CACD,MAAM,UAAkB,OAAO;CAC/B,MAAM,SAAS,OAAO,IAAI,KAAK,OAAO;CACtC,MAAM,WAAW,eAAe,MAAM,SACrC,OAAO,QAAQ,IAAI,IAAI,MAAM,IAAI,EAAE,eAAe,SAAS,KAAK,IAAI,CAAC,CACtE;CACA,OAAO,MAAM,MACZ,eAAe,MAAM,eAAe,OAAO,aAAa,QAAQ,CAAC,GACjE,MAAM,QAAQ,SAAS,EACtB,QAAQ,OAAO,IAAI,aAAa;EAC/B,MAAM,OAAO,OAAO;EACpB,MAAM,WAAW,OAAO,IAAI,UAAU,QAAQ,IAAI;EAClD,OAAO,KAAK,SAAS,SAAS;CAC/B,CAAC,EACF,CAAC,CACF;AACD,CAAC,CACF;;;;;AAMD,MAAM,cACJ,UACM,SACN,KAAK,UAAU,IAAI,KACnB,MAAM,cAAc,KAAK,KAAK,MAC7B,MAAM,SAAS,KAAK,KAAK,KAAK,MAAM,QAAQ,KAAK,KAAK,KACpD,OAAO,SAAS,YAAY,KAAK,0BAA0B,KAAK,KAAK,IACrE,OAAO;;;;;AAMZ,MAAa,gBACZ,MACA,QACA,UAEA,OAAO,IAAI,aAAa;CACvB,MAAM,KAAK,OAAO,WAAW;CAC7B,MAAM,OAAO,OAAO,KAAK;CACzB,MAAM,QAAQ,OAAO,SAAS,KAAW;CAGzC,MAAM,SAAS,OAAO,UAIpB,UACA,GACE,MAAM,KAAK,QAAQ,IAAI,GAAG,EAC1B,eAAe,SAAS,WAAW,OAAO,OAAO,IAAI,EACtD,CAAC,CAAC,CACD,KACA,OAAO,QACL,UAAU,KAAK,SAAS,MAAM,IAAI,MAAM,KAAK,SAAS,IAAI,CAC5D,GACA,OAAO,iBAAiB,WAAW,MAAM,SAAS,OAAO,MAAM,CAAC,GAChE,OAAO,QACP,OAAO,OAAO,WAAW,IAAI,CAAC,GAC9B,OAAO,cACL,UAAU,CAAC,MAAM,cAAc,KAAK,IACpC,UAAU,OAAO,SAAS,YAAY,KAAK,UAAU,KAAK,CAC5D,GAEA,OAAO,QAAQ,OAAO,MAAM,UAAU,CAAC,GACvC,OAAO,OACR,GACF,EAAE,YAAY,GAAG,CAClB;CAGA,OAAO,OAAO,SACb;EACC,OAAO,GAAG,QAAQ,KAAA,CAAS;EAC3B,OAAO,WAAW,SAAS,MAAM,KAAK,CAAC;EACvC,OAAO,KAAK,UAAU,CAAC,CAAC,KAAK,OAAO,KAAK,CAAC,CAAC;CAC5C,GACA,EAAE,aAAa,YAAY,CAC5B,CAAC,CAAC,KACD,OAAO,iBAEN,OAAO,MAAM,YAAY,CAAC,CAAC,KAC1B,OAAO,QAAQ,MAAM,GACrB,OAAO,SAAS,YAAa,UAAU,QAAQ,OAAO,IAAK,GAC3D,OAAO,cACL,UAAU,CAAC,MAAM,cAAc,KAAK,IACpC,UAAU,OAAO,SAAS,aAAa,KAAK,UAAU,KAAK,CAC7D,CACD,CACD,GACA,OAAO,cACL,UAAU,CAAC,MAAM,cAAc,KAAK,IACpC,UAAU,OAAO,SAAS,oBAAoB,QAAQ,KAAK,CAC7D,CACD;AACD,CAAC,CAAC,CAAC,KAEF,OAAO,OAAO,WAAW,IAAI,CAAC,CAC/B"}
@@ -0,0 +1,12 @@
1
+ import { LogLevel, References, Schema } from "effect";
2
+ import { Intercept } from "@graftland/loader";
3
+ // src/internal/intercepts.ts
4
+ /** References that can be configured by stock CLI entries. */
5
+ const registrations = [Intercept.make(References.CurrentLogAnnotations, Schema.Record(Schema.String, Schema.Unknown).annotate({ description: "Shallowly merges with outer annotations; configured keys win." }), (outer, patch) => ({
6
+ ...outer,
7
+ ...patch
8
+ })), Intercept.make(References.MinimumLogLevel, Schema.Literals(LogLevel.values).annotate({ description: "Replaces the outer minimum log level." }), (_, level) => level)];
9
+ const layer = Intercept.layer(registrations);
10
+ export { layer, registrations };
11
+
12
+ //# sourceMappingURL=intercepts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intercepts.js","names":[],"sources":["../../src/internal/intercepts.ts"],"sourcesContent":["import { Intercept } from \"@graftland/loader\"\nimport { LogLevel, References, Schema } from \"effect\"\n\n/** References that can be configured by stock CLI entries. */\nexport const registrations = [\n\tIntercept.make(\n\t\tReferences.CurrentLogAnnotations,\n\t\tSchema.Record(Schema.String, Schema.Unknown).annotate({\n\t\t\tdescription:\n\t\t\t\t\"Shallowly merges with outer annotations; configured keys win.\",\n\t\t}),\n\t\t(outer, patch) => ({ ...outer, ...patch }),\n\t),\n\tIntercept.make(\n\t\tReferences.MinimumLogLevel,\n\t\tSchema.Literals(LogLevel.values).annotate({\n\t\t\tdescription: \"Replaces the outer minimum log level.\",\n\t\t}),\n\t\t(_, level) => level,\n\t),\n]\n\nexport const layer = Intercept.layer(registrations)\n"],"mappings":";;;;AAIA,MAAa,gBAAgB,CAC5B,UAAU,KACT,WAAW,uBACX,OAAO,OAAO,OAAO,QAAQ,OAAO,OAAO,CAAC,CAAC,SAAS,EACrD,aACC,gEACF,CAAC,IACA,OAAO,WAAW;CAAE,GAAG;CAAO,GAAG;AAAM,EACzC,GACA,UAAU,KACT,WAAW,iBACX,OAAO,SAAS,SAAS,MAAM,CAAC,CAAC,SAAS,EACzC,aAAa,wCACd,CAAC,IACA,GAAG,UAAU,KACf,CACD;AAEA,MAAa,QAAQ,UAAU,MAAM,aAAa"}
@@ -0,0 +1,25 @@
1
+ import { Array, Cause, Effect, Exit, Stream, String } from "effect";
2
+ // src/internal/watch.ts
3
+ const moduleExtensions = [
4
+ ".js",
5
+ ".mjs",
6
+ ".cjs",
7
+ ".ts",
8
+ ".mts",
9
+ ".cts"
10
+ ];
11
+ /** Whether a change to this file should reload plugin modules. */
12
+ const isModule = (path) => !Array.contains(String.split(/[/\\]/)(path), "node_modules") && Array.some(moduleExtensions, (extension) => String.endsWith(extension)(path));
13
+ const describe = (result) => result._tag === "Reloaded" ? Effect.logInfo(`Reloaded ${result.entries.length} plugin module(s)`) : Effect.logWarning(result.reason === "ambiguous-exports" ? `Restart required: shared plugin exports diverged in ${Array.join(result.modules, ", ")}` : result.reason === "identity-conflict" ? `Restart required: plugin replacement identities conflict in ${Array.join(result.modules, ", ")}` : `Restart required to pick up ${Array.join(result.modules, ", ")}`);
14
+ /** Owns the HMR stream and retries failed loader entries after each reload. */
15
+ const watchModules = (hmr, directory, retry) => hmr.watch([directory], {
16
+ recursive: true,
17
+ filter: isModule
18
+ }).pipe(Stream.runForEach((result) => Effect.gen(function* () {
19
+ if (result._tag === "Failed") return yield* Effect.failCause(result.cause);
20
+ yield* describe(result);
21
+ yield* retry;
22
+ }).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.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
+ export { isModule, watchModules };
24
+
25
+ //# sourceMappingURL=watch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch.js","names":[],"sources":["../../src/internal/watch.ts"],"sourcesContent":["import 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// 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":";;AAGA,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,SAGP,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"}
@@ -0,0 +1,5 @@
1
+ // package.json
2
+ var version = "0.0.0";
3
+ export { version };
4
+
5
+ //# sourceMappingURL=package.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package.js","names":[],"sources":["../package.json"],"sourcesContent":[""],"mappings":""}