@savvy-web/silk-effects 0.1.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/LICENSE +21 -0
- package/README.md +75 -0
- package/biome.d.ts +135 -0
- package/biome.js +84 -0
- package/config.d.ts +147 -0
- package/config.js +38 -0
- package/hooks.d.ts +197 -0
- package/hooks.js +96 -0
- package/index.d.ts +1045 -0
- package/index.js +6 -0
- package/package.json +86 -0
- package/publish.d.ts +260 -0
- package/publish.js +124 -0
- package/tags.d.ts +122 -0
- package/tags.js +25 -0
- package/tsdoc-metadata.json +11 -0
- package/versioning.d.ts +234 -0
- package/versioning.js +115 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Savvy Web Systems
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# @savvy-web/silk-effects
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@savvy-web/silk-effects)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
Shared [Effect](https://effect.website/) library providing Silk Suite conventions for publishability detection, versioning strategy, tag formatting, managed sections, config discovery, and Biome schema synchronization. Platform-agnostic: consumers provide their own runtime layer (Node.js, Bun, etc.).
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- Resolve publish targets from shorthand strings, URLs, or objects with sensible defaults
|
|
11
|
+
- Detect versioning strategy (single, fixed-group, independent) from changeset config
|
|
12
|
+
- Format git tags consistently based on workspace structure
|
|
13
|
+
- Manage tool-owned sections inside user-editable files without clobbering user content
|
|
14
|
+
- Discover config files using a priority-based search convention
|
|
15
|
+
- Keep Biome `$schema` URLs in sync across config files
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pnpm add @savvy-web/silk-effects effect @effect/platform @effect/platform-node
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`effect` is a peer dependency -- install it alongside the package.
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { Effect } from "effect";
|
|
29
|
+
import { TargetResolver, TargetResolverLive } from "@savvy-web/silk-effects/publish";
|
|
30
|
+
|
|
31
|
+
const targets = await Effect.runPromise(
|
|
32
|
+
Effect.gen(function* () {
|
|
33
|
+
const resolver = yield* TargetResolver;
|
|
34
|
+
return yield* resolver.resolve(["npm", "github"]);
|
|
35
|
+
}).pipe(Effect.provide(TargetResolverLive)),
|
|
36
|
+
);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Modules that access the filesystem require a platform layer:
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { NodeContext } from "@effect/platform-node";
|
|
43
|
+
import { ManagedSection, ManagedSectionLive } from "@savvy-web/silk-effects/hooks";
|
|
44
|
+
|
|
45
|
+
await Effect.runPromise(
|
|
46
|
+
Effect.gen(function* () {
|
|
47
|
+
const section = yield* ManagedSection;
|
|
48
|
+
yield* section.write(".husky/pre-commit", "silk", "\nnpx lint-staged\n");
|
|
49
|
+
}).pipe(
|
|
50
|
+
Effect.provide(ManagedSectionLive),
|
|
51
|
+
Effect.provide(NodeContext.layer),
|
|
52
|
+
),
|
|
53
|
+
);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Modules
|
|
57
|
+
|
|
58
|
+
Each module has its own entry point -- import only what you need:
|
|
59
|
+
|
|
60
|
+
| Module | Entry Point | Platform Layer | Docs |
|
|
61
|
+
| ------ | ----------- | -------------- | ---- |
|
|
62
|
+
| Publish | `@savvy-web/silk-effects/publish` | No | [docs/publish.md](./docs/publish.md) |
|
|
63
|
+
| Versioning | `@savvy-web/silk-effects/versioning` | Yes | [docs/versioning.md](./docs/versioning.md) |
|
|
64
|
+
| Tags | `@savvy-web/silk-effects/tags` | No | [docs/tags.md](./docs/tags.md) |
|
|
65
|
+
| Hooks | `@savvy-web/silk-effects/hooks` | Yes | [docs/hooks.md](./docs/hooks.md) |
|
|
66
|
+
| Config | `@savvy-web/silk-effects/config` | Yes | [docs/config.md](./docs/config.md) |
|
|
67
|
+
| Biome | `@savvy-web/silk-effects/biome` | Yes | [docs/biome.md](./docs/biome.md) |
|
|
68
|
+
|
|
69
|
+
## Documentation
|
|
70
|
+
|
|
71
|
+
For service API reference, schemas, error types, and advanced usage, see [docs/](./docs/).
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
[MIT](./LICENSE)
|
package/biome.d.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { Context } from 'effect';
|
|
2
|
+
import { Effect } from 'effect';
|
|
3
|
+
import { FileSystem } from '@effect/platform';
|
|
4
|
+
import { Layer } from 'effect';
|
|
5
|
+
import { Schema } from 'effect';
|
|
6
|
+
import { VoidIfEmpty } from 'effect/Types';
|
|
7
|
+
import { YieldableError } from 'effect/Cause';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Service that keeps the `$schema` URL in Biome config files in sync with a target version.
|
|
11
|
+
*
|
|
12
|
+
* @remarks
|
|
13
|
+
* Locates `biome.json` and `biome.jsonc` files in the working directory, then compares
|
|
14
|
+
* each file's `$schema` field against the expected `biomejs.dev` URL for the given version.
|
|
15
|
+
* `sync` writes updates in-place; `check` returns the same result without modifying files.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* const result = await Effect.runPromise(
|
|
20
|
+
* Effect.gen(function* () {
|
|
21
|
+
* const syncer = yield* BiomeSchemaSync;
|
|
22
|
+
* return yield* syncer.sync("^1.9.3");
|
|
23
|
+
* }).pipe(
|
|
24
|
+
* Effect.provide(BiomeSchemaSyncLive),
|
|
25
|
+
* Effect.provide(NodeContext.layer),
|
|
26
|
+
* )
|
|
27
|
+
* );
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @since 0.1.0
|
|
31
|
+
*/
|
|
32
|
+
export declare class BiomeSchemaSync extends BiomeSchemaSync_base {
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
declare const BiomeSchemaSync_base: Context.TagClass<BiomeSchemaSync, "@savvy-web/silk-effects/BiomeSchemaSync", {
|
|
36
|
+
/**
|
|
37
|
+
* Update the `$schema` URL in all located Biome config files to match `version`.
|
|
38
|
+
*
|
|
39
|
+
* @param version - Target Biome version (range operators are stripped automatically).
|
|
40
|
+
* @param options - Optional `cwd` and `gitignore` overrides.
|
|
41
|
+
* @returns An `Effect` that succeeds with a {@link BiomeSyncResult} or fails with {@link BiomeSyncError}.
|
|
42
|
+
*
|
|
43
|
+
* @since 0.1.0
|
|
44
|
+
*/
|
|
45
|
+
readonly sync: (version: string, options?: {
|
|
46
|
+
cwd?: string;
|
|
47
|
+
gitignore?: boolean;
|
|
48
|
+
}) => Effect.Effect<BiomeSyncResult, BiomeSyncError>;
|
|
49
|
+
/**
|
|
50
|
+
* Check whether the `$schema` URL in Biome config files is current, without writing any changes.
|
|
51
|
+
*
|
|
52
|
+
* @param version - Target Biome version (range operators are stripped automatically).
|
|
53
|
+
* @param options - Optional `cwd` and `gitignore` overrides.
|
|
54
|
+
* @returns An `Effect` that succeeds with a {@link BiomeSyncResult} or fails with {@link BiomeSyncError}.
|
|
55
|
+
* Files that would be updated appear in `updated`; no disk writes occur.
|
|
56
|
+
*
|
|
57
|
+
* @since 0.1.0
|
|
58
|
+
*/
|
|
59
|
+
readonly check: (version: string, options?: {
|
|
60
|
+
cwd?: string;
|
|
61
|
+
gitignore?: boolean;
|
|
62
|
+
}) => Effect.Effect<BiomeSyncResult, BiomeSyncError>;
|
|
63
|
+
}>;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Live implementation of {@link BiomeSchemaSync}.
|
|
67
|
+
*
|
|
68
|
+
* @remarks
|
|
69
|
+
* Requires `FileSystem` from `@effect/platform`. Provide `NodeContext.layer` or
|
|
70
|
+
* `BunContext.layer` to satisfy this dependency.
|
|
71
|
+
*
|
|
72
|
+
* @since 0.1.0
|
|
73
|
+
*/
|
|
74
|
+
export declare const BiomeSchemaSyncLive: Layer.Layer<BiomeSchemaSync, never, FileSystem.FileSystem>;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Raised when a Biome config file cannot be read or its `$schema` URL cannot be updated.
|
|
78
|
+
*
|
|
79
|
+
* @remarks
|
|
80
|
+
* Returned by {@link BiomeSchemaSync.sync} and {@link BiomeSchemaSync.check} when
|
|
81
|
+
* a `biome.json` or `biome.jsonc` file exists but cannot be read, contains invalid JSON,
|
|
82
|
+
* or cannot be written back to disk.
|
|
83
|
+
*
|
|
84
|
+
* @since 0.1.0
|
|
85
|
+
*/
|
|
86
|
+
export declare class BiomeSyncError extends BiomeSyncError_base<{
|
|
87
|
+
readonly path: string;
|
|
88
|
+
readonly reason: string;
|
|
89
|
+
}> {
|
|
90
|
+
get message(): string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
declare const BiomeSyncError_base: new <A extends Record<string, any> = {}>(args: VoidIfEmpty< { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => YieldableError & {
|
|
94
|
+
readonly _tag: "BiomeSyncError";
|
|
95
|
+
} & Readonly<A>;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Options for {@link BiomeSchemaSync} operations.
|
|
99
|
+
*
|
|
100
|
+
* @remarks
|
|
101
|
+
* `cwd` overrides the working directory used to locate `biome.json` / `biome.jsonc`.
|
|
102
|
+
* `gitignore` is reserved for future use to skip gitignored config files (defaults to `true`).
|
|
103
|
+
*
|
|
104
|
+
* @since 0.1.0
|
|
105
|
+
*/
|
|
106
|
+
export declare const BiomeSyncOptions: Schema.Struct<{
|
|
107
|
+
cwd: Schema.optional<typeof Schema.String>;
|
|
108
|
+
gitignore: Schema.optionalWith<typeof Schema.Boolean, {
|
|
109
|
+
default: () => true;
|
|
110
|
+
}>;
|
|
111
|
+
}>;
|
|
112
|
+
|
|
113
|
+
/** @since 0.1.0 */
|
|
114
|
+
export declare type BiomeSyncOptions = typeof BiomeSyncOptions.Type;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Result of a Biome schema URL sync or check operation.
|
|
118
|
+
*
|
|
119
|
+
* @remarks
|
|
120
|
+
* - `updated` — paths of config files whose `$schema` URL was changed (or would be changed on `check`).
|
|
121
|
+
* - `skipped` — paths of config files with no `$schema` field or a non-biomejs.dev URL.
|
|
122
|
+
* - `current` — paths of config files already pointing to the expected schema URL.
|
|
123
|
+
*
|
|
124
|
+
* @since 0.1.0
|
|
125
|
+
*/
|
|
126
|
+
export declare const BiomeSyncResult: Schema.Struct<{
|
|
127
|
+
updated: Schema.Array$<typeof Schema.String>;
|
|
128
|
+
skipped: Schema.Array$<typeof Schema.String>;
|
|
129
|
+
current: Schema.Array$<typeof Schema.String>;
|
|
130
|
+
}>;
|
|
131
|
+
|
|
132
|
+
/** @since 0.1.0 */
|
|
133
|
+
export declare type BiomeSyncResult = typeof BiomeSyncResult.Type;
|
|
134
|
+
|
|
135
|
+
export { }
|
package/biome.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { FileSystem } from "@effect/platform";
|
|
2
|
+
import { Context, Data, Effect, Layer } from "effect";
|
|
3
|
+
import { parse } from "jsonc-effect";
|
|
4
|
+
class BiomeSyncError extends Data.TaggedError("BiomeSyncError") {
|
|
5
|
+
get message() {
|
|
6
|
+
return `Failed to sync biome schema in ${this.path}: ${this.reason}`;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
function extractSemver(version) {
|
|
10
|
+
return version.replace(/^[\^~>=<v]+/, "");
|
|
11
|
+
}
|
|
12
|
+
function buildSchemaUrl(version) {
|
|
13
|
+
return `https://biomejs.dev/schemas/${version}/schema.json`;
|
|
14
|
+
}
|
|
15
|
+
const BIOME_SCHEMA_HOSTNAME = "biomejs.dev";
|
|
16
|
+
function findBiomeConfigs(cwd, fs) {
|
|
17
|
+
const candidates = [
|
|
18
|
+
`${cwd}/biome.json`,
|
|
19
|
+
`${cwd}/biome.jsonc`
|
|
20
|
+
];
|
|
21
|
+
return Effect.gen(function*() {
|
|
22
|
+
const results = [];
|
|
23
|
+
for (const candidate of candidates){
|
|
24
|
+
const exists = yield* fs.exists(candidate).pipe(Effect.orElseSucceed(()=>false));
|
|
25
|
+
if (exists) results.push(candidate);
|
|
26
|
+
}
|
|
27
|
+
return results;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
class BiomeSchemaSync extends Context.Tag("@savvy-web/silk-effects/BiomeSchemaSync")() {
|
|
31
|
+
}
|
|
32
|
+
const BiomeSchemaSyncLive = Layer.effect(BiomeSchemaSync, Effect.gen(function*() {
|
|
33
|
+
const fs = yield* FileSystem.FileSystem;
|
|
34
|
+
const run = (version, options, write)=>Effect.gen(function*() {
|
|
35
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
36
|
+
const semver = extractSemver(version);
|
|
37
|
+
const expectedUrl = buildSchemaUrl(semver);
|
|
38
|
+
const configs = yield* findBiomeConfigs(cwd, fs);
|
|
39
|
+
const updated = [];
|
|
40
|
+
const skipped = [];
|
|
41
|
+
const current = [];
|
|
42
|
+
for (const configPath of configs){
|
|
43
|
+
const raw = yield* fs.readFileString(configPath).pipe(Effect.mapError((cause)=>new BiomeSyncError({
|
|
44
|
+
path: configPath,
|
|
45
|
+
reason: String(cause)
|
|
46
|
+
})));
|
|
47
|
+
const parsed = yield* parse(raw).pipe(Effect.mapError((e)=>new BiomeSyncError({
|
|
48
|
+
path: configPath,
|
|
49
|
+
reason: `Failed to parse JSONC: ${String(e)}`
|
|
50
|
+
})));
|
|
51
|
+
const schema = parsed.$schema;
|
|
52
|
+
if ("string" != typeof schema) {
|
|
53
|
+
skipped.push(configPath);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (!schema.includes(BIOME_SCHEMA_HOSTNAME)) {
|
|
57
|
+
skipped.push(configPath);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (schema === expectedUrl) {
|
|
61
|
+
current.push(configPath);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (write) {
|
|
65
|
+
const updated_content = raw.replaceAll(schema, expectedUrl);
|
|
66
|
+
yield* fs.writeFileString(configPath, updated_content).pipe(Effect.mapError((cause)=>new BiomeSyncError({
|
|
67
|
+
path: configPath,
|
|
68
|
+
reason: String(cause)
|
|
69
|
+
})));
|
|
70
|
+
}
|
|
71
|
+
updated.push(configPath);
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
updated,
|
|
75
|
+
skipped,
|
|
76
|
+
current
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
sync: (version, options)=>run(version, options, true),
|
|
81
|
+
check: (version, options)=>run(version, options, false)
|
|
82
|
+
};
|
|
83
|
+
}));
|
|
84
|
+
export { BiomeSchemaSync, BiomeSchemaSyncLive, BiomeSyncError };
|
package/config.d.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { Context } from 'effect';
|
|
2
|
+
import { Effect } from 'effect';
|
|
3
|
+
import { FileSystem } from '@effect/platform';
|
|
4
|
+
import { Layer } from 'effect';
|
|
5
|
+
import { Schema } from 'effect';
|
|
6
|
+
import { VoidIfEmpty } from 'effect/Types';
|
|
7
|
+
import { YieldableError } from 'effect/Cause';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Service that locates named config files within a workspace using priority-ordered search paths.
|
|
11
|
+
*
|
|
12
|
+
* @remarks
|
|
13
|
+
* Search priority (highest to lowest):
|
|
14
|
+
* 1. `lib/configs/{name}` — shared config provided by a dependency package.
|
|
15
|
+
* 2. `{cwd}/{name}` — local override at the workspace root.
|
|
16
|
+
*
|
|
17
|
+
* Missing files are silently skipped; only files that actually exist are returned.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* const result = await Effect.runPromise(
|
|
22
|
+
* Effect.gen(function* () {
|
|
23
|
+
* const discovery = yield* ConfigDiscovery;
|
|
24
|
+
* return yield* discovery.find("biome.json");
|
|
25
|
+
* }).pipe(
|
|
26
|
+
* Effect.provide(ConfigDiscoveryLive),
|
|
27
|
+
* Effect.provide(NodeContext.layer),
|
|
28
|
+
* )
|
|
29
|
+
* );
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @since 0.1.0
|
|
33
|
+
*/
|
|
34
|
+
export declare class ConfigDiscovery extends ConfigDiscovery_base {
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
declare const ConfigDiscovery_base: Context.TagClass<ConfigDiscovery, "@savvy-web/silk-effects/ConfigDiscovery", {
|
|
38
|
+
/**
|
|
39
|
+
* Return the highest-priority {@link ConfigLocation} for the given config file name,
|
|
40
|
+
* or `null` when none of the candidate paths exist.
|
|
41
|
+
*
|
|
42
|
+
* @param name - Config file name (e.g. `"biome.json"`).
|
|
43
|
+
* @param options - Optional `cwd` override for path resolution.
|
|
44
|
+
* @returns An `Effect` that always succeeds with a {@link ConfigLocation} or `null`.
|
|
45
|
+
*
|
|
46
|
+
* @since 0.1.0
|
|
47
|
+
*/
|
|
48
|
+
readonly find: (name: string, options?: {
|
|
49
|
+
cwd?: string;
|
|
50
|
+
}) => Effect.Effect<ConfigLocation | null>;
|
|
51
|
+
/**
|
|
52
|
+
* Return all existing {@link ConfigLocation} entries for the given config file name,
|
|
53
|
+
* ordered from highest to lowest priority.
|
|
54
|
+
*
|
|
55
|
+
* @param name - Config file name (e.g. `"biome.json"`).
|
|
56
|
+
* @param options - Optional `cwd` override for path resolution.
|
|
57
|
+
* @returns An `Effect` that always succeeds with an array of {@link ConfigLocation} records.
|
|
58
|
+
*
|
|
59
|
+
* @since 0.1.0
|
|
60
|
+
*/
|
|
61
|
+
readonly findAll: (name: string, options?: {
|
|
62
|
+
cwd?: string;
|
|
63
|
+
}) => Effect.Effect<ReadonlyArray<ConfigLocation>>;
|
|
64
|
+
}>;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Live implementation of {@link ConfigDiscovery}.
|
|
68
|
+
*
|
|
69
|
+
* @remarks
|
|
70
|
+
* Requires `FileSystem` from `@effect/platform`. Provide `NodeContext.layer` or
|
|
71
|
+
* `BunContext.layer` to satisfy this dependency.
|
|
72
|
+
*
|
|
73
|
+
* @since 0.1.0
|
|
74
|
+
*/
|
|
75
|
+
export declare const ConfigDiscoveryLive: Layer.Layer<ConfigDiscovery, never, FileSystem.FileSystem>;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Options passed to config discovery methods.
|
|
79
|
+
*
|
|
80
|
+
* @remarks
|
|
81
|
+
* `cwd` overrides the working directory for path resolution (defaults to `process.cwd()`).
|
|
82
|
+
* `tool` is reserved for future use as a tool-specific discovery hint.
|
|
83
|
+
*
|
|
84
|
+
* @since 0.1.0
|
|
85
|
+
*/
|
|
86
|
+
export declare const ConfigDiscoveryOptions: Schema.Struct<{
|
|
87
|
+
cwd: Schema.optional<typeof Schema.String>;
|
|
88
|
+
tool: Schema.optional<typeof Schema.String>;
|
|
89
|
+
}>;
|
|
90
|
+
|
|
91
|
+
/** @since 0.1.0 */
|
|
92
|
+
export declare type ConfigDiscoveryOptions = typeof ConfigDiscoveryOptions.Type;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The resolved location of a discovered config file.
|
|
96
|
+
*
|
|
97
|
+
* @remarks
|
|
98
|
+
* Produced by {@link ConfigDiscovery.find} and {@link ConfigDiscovery.findAll}.
|
|
99
|
+
* `path` is the absolute file path; `source` indicates how it was discovered.
|
|
100
|
+
*
|
|
101
|
+
* @since 0.1.0
|
|
102
|
+
*/
|
|
103
|
+
export declare const ConfigLocation: Schema.Struct<{
|
|
104
|
+
path: typeof Schema.String;
|
|
105
|
+
source: Schema.Literal<["lib", "root", "cosmiconfig"]>;
|
|
106
|
+
}>;
|
|
107
|
+
|
|
108
|
+
/** @since 0.1.0 */
|
|
109
|
+
export declare type ConfigLocation = typeof ConfigLocation.Type;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Raised when a config file cannot be located in any of the expected locations.
|
|
113
|
+
*
|
|
114
|
+
* @remarks
|
|
115
|
+
* Returned by consumers that require a config file to exist. {@link ConfigDiscovery.find}
|
|
116
|
+
* itself returns `null` instead of failing — callers that need a hard failure should
|
|
117
|
+
* map `null` to this error.
|
|
118
|
+
*
|
|
119
|
+
* @since 0.1.0
|
|
120
|
+
*/
|
|
121
|
+
export declare class ConfigNotFoundError extends ConfigNotFoundError_base<{
|
|
122
|
+
readonly name: string;
|
|
123
|
+
readonly searchedPaths: ReadonlyArray<string>;
|
|
124
|
+
}> {
|
|
125
|
+
get message(): string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
declare const ConfigNotFoundError_base: new <A extends Record<string, any> = {}>(args: VoidIfEmpty< { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => YieldableError & {
|
|
129
|
+
readonly _tag: "ConfigNotFoundError";
|
|
130
|
+
} & Readonly<A>;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The discovery strategy used to locate a config file.
|
|
134
|
+
*
|
|
135
|
+
* @remarks
|
|
136
|
+
* - `"lib"` — found under `lib/configs/{name}` relative to the workspace root.
|
|
137
|
+
* - `"root"` — found directly in the workspace root as `{name}`.
|
|
138
|
+
* - `"cosmiconfig"` — reserved for future cosmiconfig-based discovery.
|
|
139
|
+
*
|
|
140
|
+
* @since 0.1.0
|
|
141
|
+
*/
|
|
142
|
+
export declare const ConfigSource: Schema.Literal<["lib", "root", "cosmiconfig"]>;
|
|
143
|
+
|
|
144
|
+
/** @since 0.1.0 */
|
|
145
|
+
export declare type ConfigSource = typeof ConfigSource.Type;
|
|
146
|
+
|
|
147
|
+
export { }
|
package/config.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { FileSystem } from "@effect/platform";
|
|
2
|
+
import { Context, Data, Effect, Layer } from "effect";
|
|
3
|
+
class ConfigDiscovery extends Context.Tag("@savvy-web/silk-effects/ConfigDiscovery")() {
|
|
4
|
+
}
|
|
5
|
+
function safeExists(fs, path) {
|
|
6
|
+
return fs.exists(path).pipe(Effect.orElseSucceed(()=>false));
|
|
7
|
+
}
|
|
8
|
+
const ConfigDiscoveryLive = Layer.effect(ConfigDiscovery, Effect.gen(function*() {
|
|
9
|
+
const fs = yield* FileSystem.FileSystem;
|
|
10
|
+
const findAll = (name, options)=>Effect.gen(function*() {
|
|
11
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
12
|
+
const results = [];
|
|
13
|
+
const libPath = `${cwd}/lib/configs/${name}`;
|
|
14
|
+
const libExists = yield* safeExists(fs, libPath);
|
|
15
|
+
if (libExists) results.push({
|
|
16
|
+
path: libPath,
|
|
17
|
+
source: "lib"
|
|
18
|
+
});
|
|
19
|
+
const rootPath = `${cwd}/${name}`;
|
|
20
|
+
const rootExists = yield* safeExists(fs, rootPath);
|
|
21
|
+
if (rootExists) results.push({
|
|
22
|
+
path: rootPath,
|
|
23
|
+
source: "root"
|
|
24
|
+
});
|
|
25
|
+
return results;
|
|
26
|
+
});
|
|
27
|
+
const find = (name, options)=>findAll(name, options).pipe(Effect.map((results)=>results[0] ?? null));
|
|
28
|
+
return {
|
|
29
|
+
find,
|
|
30
|
+
findAll
|
|
31
|
+
};
|
|
32
|
+
}));
|
|
33
|
+
class ConfigNotFoundError extends Data.TaggedError("ConfigNotFoundError") {
|
|
34
|
+
get message() {
|
|
35
|
+
return `Config '${this.name}' not found. Searched: ${this.searchedPaths.join(", ")}`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export { ConfigDiscovery, ConfigDiscoveryLive, ConfigNotFoundError };
|