@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/hooks.d.ts
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
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
|
+
* Comment syntax used to write managed section markers.
|
|
11
|
+
*
|
|
12
|
+
* @remarks
|
|
13
|
+
* - `"#"` — shell/YAML style, suitable for hook scripts and `.env` files.
|
|
14
|
+
* - `"//"` — C-style, suitable for JavaScript/TypeScript files.
|
|
15
|
+
*
|
|
16
|
+
* @since 0.1.0
|
|
17
|
+
*/
|
|
18
|
+
export declare const CommentStyle: Schema.Literal<["#", "//"]>;
|
|
19
|
+
|
|
20
|
+
/** @since 0.1.0 */
|
|
21
|
+
export declare type CommentStyle = typeof CommentStyle.Type;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Service providing managed section operations for tool-owned regions
|
|
25
|
+
* within user-editable files (e.g. husky hooks).
|
|
26
|
+
*
|
|
27
|
+
* @remarks
|
|
28
|
+
* A managed section is a delimited block in a file bounded by BEGIN/END marker comments.
|
|
29
|
+
* The markers embed the tool name so multiple tools can manage independent sections in
|
|
30
|
+
* the same file. User content outside the markers is always preserved.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* const result = await Effect.runPromise(
|
|
35
|
+
* Effect.gen(function* () {
|
|
36
|
+
* const section = yield* ManagedSection;
|
|
37
|
+
* yield* section.write(".husky/pre-commit", "silk", "\nnpx lint-staged\n");
|
|
38
|
+
* return yield* section.read(".husky/pre-commit", "silk");
|
|
39
|
+
* }).pipe(
|
|
40
|
+
* Effect.provide(ManagedSectionLive),
|
|
41
|
+
* Effect.provide(NodeContext.layer),
|
|
42
|
+
* )
|
|
43
|
+
* );
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @since 0.1.0
|
|
47
|
+
*/
|
|
48
|
+
export declare class ManagedSection extends ManagedSection_base {
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
declare const ManagedSection_base: Context.TagClass<ManagedSection, "@savvy-web/silk-effects/ManagedSection", {
|
|
52
|
+
/**
|
|
53
|
+
* Read and parse the managed section from a file.
|
|
54
|
+
*
|
|
55
|
+
* @param path - Absolute path to the file.
|
|
56
|
+
* @param toolName - Tool identifier embedded in the section markers.
|
|
57
|
+
* @param commentStyle - Comment prefix to use (`"#"` or `"//"`, defaults to `"#"`).
|
|
58
|
+
* @returns An `Effect` that succeeds with a {@link ManagedSectionResult} when markers
|
|
59
|
+
* are found, `null` when the file has no markers, or fails with
|
|
60
|
+
* {@link ManagedSectionParseError} on I/O errors.
|
|
61
|
+
*
|
|
62
|
+
* @since 0.1.0
|
|
63
|
+
*/
|
|
64
|
+
readonly read: (path: string, toolName: string, commentStyle?: string) => Effect.Effect<ManagedSectionResult | null, ManagedSectionParseError>;
|
|
65
|
+
/**
|
|
66
|
+
* Write managed content to a file.
|
|
67
|
+
*
|
|
68
|
+
* @remarks
|
|
69
|
+
* - Replaces the existing managed section when markers are already present.
|
|
70
|
+
* - Appends a new managed section when the file exists but has no markers.
|
|
71
|
+
* - Creates the file when it does not exist.
|
|
72
|
+
*
|
|
73
|
+
* @param path - Absolute path to the file.
|
|
74
|
+
* @param toolName - Tool identifier embedded in the section markers.
|
|
75
|
+
* @param content - Content to place inside the managed section (between markers).
|
|
76
|
+
* @param commentStyle - Comment prefix to use (`"#"` or `"//"`, defaults to `"#"`).
|
|
77
|
+
* @returns An `Effect` that succeeds with `void` or fails with {@link ManagedSectionWriteError}.
|
|
78
|
+
*
|
|
79
|
+
* @since 0.1.0
|
|
80
|
+
*/
|
|
81
|
+
readonly write: (path: string, toolName: string, content: string, commentStyle?: string) => Effect.Effect<void, ManagedSectionWriteError>;
|
|
82
|
+
/**
|
|
83
|
+
* Read-then-write convenience method that replaces managed content while
|
|
84
|
+
* preserving surrounding user content.
|
|
85
|
+
*
|
|
86
|
+
* @param path - Absolute path to the file.
|
|
87
|
+
* @param toolName - Tool identifier embedded in the section markers.
|
|
88
|
+
* @param content - Replacement content for the managed section.
|
|
89
|
+
* @param commentStyle - Comment prefix to use (`"#"` or `"//"`, defaults to `"#"`).
|
|
90
|
+
* @returns An `Effect` that succeeds with `void` or fails with {@link ManagedSectionWriteError}.
|
|
91
|
+
*
|
|
92
|
+
* @since 0.1.0
|
|
93
|
+
*/
|
|
94
|
+
readonly update: (path: string, toolName: string, content: string, commentStyle?: string) => Effect.Effect<void, ManagedSectionWriteError>;
|
|
95
|
+
/**
|
|
96
|
+
* Return `true` when the file contains both BEGIN and END markers for the given tool.
|
|
97
|
+
*
|
|
98
|
+
* @param path - Absolute path to the file.
|
|
99
|
+
* @param toolName - Tool identifier to search for in the markers.
|
|
100
|
+
* @param commentStyle - Comment prefix to match (`"#"` or `"//"`, defaults to `"#"`).
|
|
101
|
+
* @returns An `Effect` that always succeeds with a boolean.
|
|
102
|
+
*
|
|
103
|
+
* @since 0.1.0
|
|
104
|
+
*/
|
|
105
|
+
readonly isManaged: (path: string, toolName: string, commentStyle?: string) => Effect.Effect<boolean>;
|
|
106
|
+
}>;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Live implementation of {@link ManagedSection} backed by `@effect/platform` FileSystem.
|
|
110
|
+
*
|
|
111
|
+
* @remarks
|
|
112
|
+
* Requires `FileSystem` from `@effect/platform`. Provide `NodeContext.layer` or
|
|
113
|
+
* `BunContext.layer` to satisfy this dependency.
|
|
114
|
+
*
|
|
115
|
+
* @since 0.1.0
|
|
116
|
+
*/
|
|
117
|
+
export declare const ManagedSectionLive: Layer.Layer<ManagedSection, never, FileSystem.FileSystem>;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Options controlling how managed sections are identified in a file.
|
|
121
|
+
*
|
|
122
|
+
* @remarks
|
|
123
|
+
* `toolName` is embedded in the BEGIN/END markers so multiple tools can coexist
|
|
124
|
+
* in the same file without collision. `commentStyle` defaults to `"#"`.
|
|
125
|
+
*
|
|
126
|
+
* @since 0.1.0
|
|
127
|
+
*/
|
|
128
|
+
export declare const ManagedSectionOptions: Schema.Struct<{
|
|
129
|
+
toolName: typeof Schema.String;
|
|
130
|
+
commentStyle: Schema.optionalWith<Schema.Literal<["#", "//"]>, {
|
|
131
|
+
default: () => "#";
|
|
132
|
+
}>;
|
|
133
|
+
}>;
|
|
134
|
+
|
|
135
|
+
/** @since 0.1.0 */
|
|
136
|
+
export declare type ManagedSectionOptions = typeof ManagedSectionOptions.Type;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Raised when a managed section cannot be parsed from a file.
|
|
140
|
+
*
|
|
141
|
+
* @remarks
|
|
142
|
+
* Returned by {@link ManagedSection.read} when the file exists but its content
|
|
143
|
+
* cannot be read (e.g. a filesystem permission error).
|
|
144
|
+
*
|
|
145
|
+
* @since 0.1.0
|
|
146
|
+
*/
|
|
147
|
+
export declare class ManagedSectionParseError extends ManagedSectionParseError_base<{
|
|
148
|
+
readonly path: string;
|
|
149
|
+
readonly reason: string;
|
|
150
|
+
}> {
|
|
151
|
+
get message(): string;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
declare const ManagedSectionParseError_base: new <A extends Record<string, any> = {}>(args: VoidIfEmpty< { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => YieldableError & {
|
|
155
|
+
readonly _tag: "ManagedSectionParseError";
|
|
156
|
+
} & Readonly<A>;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Parsed result of reading a managed section from a file.
|
|
160
|
+
*
|
|
161
|
+
* @remarks
|
|
162
|
+
* Produced by {@link ManagedSection.read}. The `before` and `after` strings
|
|
163
|
+
* contain the file content surrounding the managed block; `managed` holds the
|
|
164
|
+
* content between the BEGIN and END markers (excluding the markers themselves).
|
|
165
|
+
*
|
|
166
|
+
* @since 0.1.0
|
|
167
|
+
*/
|
|
168
|
+
export declare const ManagedSectionResult: Schema.Struct<{
|
|
169
|
+
before: typeof Schema.String;
|
|
170
|
+
managed: typeof Schema.String;
|
|
171
|
+
after: typeof Schema.String;
|
|
172
|
+
}>;
|
|
173
|
+
|
|
174
|
+
/** @since 0.1.0 */
|
|
175
|
+
export declare type ManagedSectionResult = typeof ManagedSectionResult.Type;
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Raised when a managed section cannot be written to a file.
|
|
179
|
+
*
|
|
180
|
+
* @remarks
|
|
181
|
+
* Returned by {@link ManagedSection.write} and {@link ManagedSection.update} when
|
|
182
|
+
* the file cannot be read for content replacement or when the write itself fails.
|
|
183
|
+
*
|
|
184
|
+
* @since 0.1.0
|
|
185
|
+
*/
|
|
186
|
+
export declare class ManagedSectionWriteError extends ManagedSectionWriteError_base<{
|
|
187
|
+
readonly path: string;
|
|
188
|
+
readonly reason: string;
|
|
189
|
+
}> {
|
|
190
|
+
get message(): string;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
declare const ManagedSectionWriteError_base: new <A extends Record<string, any> = {}>(args: VoidIfEmpty< { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => YieldableError & {
|
|
194
|
+
readonly _tag: "ManagedSectionWriteError";
|
|
195
|
+
} & Readonly<A>;
|
|
196
|
+
|
|
197
|
+
export { }
|
package/hooks.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Context, Data, Effect, Layer } from "effect";
|
|
2
|
+
import { FileSystem } from "@effect/platform";
|
|
3
|
+
class ManagedSectionParseError extends Data.TaggedError("ManagedSectionParseError") {
|
|
4
|
+
get message() {
|
|
5
|
+
return `Failed to parse managed section in ${this.path}: ${this.reason}`;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
class ManagedSectionWriteError extends Data.TaggedError("ManagedSectionWriteError") {
|
|
9
|
+
get message() {
|
|
10
|
+
return `Failed to write managed section to ${this.path}: ${this.reason}`;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function beginMarker(toolName, commentStyle) {
|
|
14
|
+
return `${commentStyle} --- BEGIN ${toolName.toUpperCase()} MANAGED SECTION ---`;
|
|
15
|
+
}
|
|
16
|
+
function endMarker(toolName, commentStyle) {
|
|
17
|
+
return `${commentStyle} --- END ${toolName.toUpperCase()} MANAGED SECTION ---`;
|
|
18
|
+
}
|
|
19
|
+
function parseContent(content, toolName, commentStyle) {
|
|
20
|
+
const begin = beginMarker(toolName, commentStyle);
|
|
21
|
+
const end = endMarker(toolName, commentStyle);
|
|
22
|
+
const beginIndex = content.indexOf(begin);
|
|
23
|
+
const endIndex = content.indexOf(end);
|
|
24
|
+
if (-1 === beginIndex || -1 === endIndex || endIndex <= beginIndex) return null;
|
|
25
|
+
const before = content.slice(0, beginIndex);
|
|
26
|
+
const managed = content.slice(beginIndex + begin.length, endIndex);
|
|
27
|
+
const after = content.slice(endIndex + end.length);
|
|
28
|
+
return {
|
|
29
|
+
before,
|
|
30
|
+
managed,
|
|
31
|
+
after
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function assembleContent(before, managed, after, toolName, commentStyle) {
|
|
35
|
+
const begin = beginMarker(toolName, commentStyle);
|
|
36
|
+
const end = endMarker(toolName, commentStyle);
|
|
37
|
+
return `${before}${begin}${managed}${end}${after}`;
|
|
38
|
+
}
|
|
39
|
+
class ManagedSection extends Context.Tag("@savvy-web/silk-effects/ManagedSection")() {
|
|
40
|
+
}
|
|
41
|
+
const ManagedSectionLive = Layer.effect(ManagedSection, Effect.gen(function*() {
|
|
42
|
+
const fs = yield* FileSystem.FileSystem;
|
|
43
|
+
const read = (path, toolName, commentStyle = "#")=>Effect.gen(function*() {
|
|
44
|
+
const exists = yield* fs.exists(path).pipe(Effect.orElseSucceed(()=>false));
|
|
45
|
+
if (!exists) return null;
|
|
46
|
+
const raw = yield* fs.readFileString(path).pipe(Effect.mapError((cause)=>new ManagedSectionParseError({
|
|
47
|
+
path,
|
|
48
|
+
reason: String(cause)
|
|
49
|
+
})));
|
|
50
|
+
return parseContent(raw, toolName, commentStyle);
|
|
51
|
+
});
|
|
52
|
+
const write = (path, toolName, content, commentStyle = "#")=>Effect.gen(function*() {
|
|
53
|
+
const exists = yield* fs.exists(path).pipe(Effect.orElseSucceed(()=>false));
|
|
54
|
+
let fileContent;
|
|
55
|
+
if (exists) {
|
|
56
|
+
const raw = yield* fs.readFileString(path).pipe(Effect.mapError((cause)=>new ManagedSectionWriteError({
|
|
57
|
+
path,
|
|
58
|
+
reason: String(cause)
|
|
59
|
+
})));
|
|
60
|
+
const parsed = parseContent(raw, toolName, commentStyle);
|
|
61
|
+
if (null !== parsed) fileContent = assembleContent(parsed.before, content, parsed.after, toolName, commentStyle);
|
|
62
|
+
else {
|
|
63
|
+
const trimmed = raw.trimEnd();
|
|
64
|
+
const begin = beginMarker(toolName, commentStyle);
|
|
65
|
+
const end = endMarker(toolName, commentStyle);
|
|
66
|
+
fileContent = `${trimmed}\n\n${begin}${content}${end}\n`;
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
const begin = beginMarker(toolName, commentStyle);
|
|
70
|
+
const end = endMarker(toolName, commentStyle);
|
|
71
|
+
fileContent = `${begin}${content}${end}\n`;
|
|
72
|
+
}
|
|
73
|
+
yield* fs.writeFileString(path, fileContent).pipe(Effect.mapError((cause)=>new ManagedSectionWriteError({
|
|
74
|
+
path,
|
|
75
|
+
reason: String(cause)
|
|
76
|
+
})));
|
|
77
|
+
});
|
|
78
|
+
const update = (path, toolName, content, commentStyle = "#")=>write(path, toolName, content, commentStyle);
|
|
79
|
+
const isManaged = (path, toolName, commentStyle = "#")=>Effect.gen(function*() {
|
|
80
|
+
const exists = yield* fs.exists(path).pipe(Effect.orElseSucceed(()=>false));
|
|
81
|
+
if (!exists) return false;
|
|
82
|
+
const raw = yield* fs.readFileString(path).pipe(Effect.orElseSucceed(()=>""));
|
|
83
|
+
const begin = beginMarker(toolName, commentStyle);
|
|
84
|
+
const end = endMarker(toolName, commentStyle);
|
|
85
|
+
const beginIdx = raw.indexOf(begin);
|
|
86
|
+
const endIdx = raw.indexOf(end);
|
|
87
|
+
return -1 !== beginIdx && -1 !== endIdx && endIdx > beginIdx;
|
|
88
|
+
});
|
|
89
|
+
return {
|
|
90
|
+
read,
|
|
91
|
+
write,
|
|
92
|
+
update,
|
|
93
|
+
isManaged
|
|
94
|
+
};
|
|
95
|
+
}));
|
|
96
|
+
export { ManagedSection, ManagedSectionLive, ManagedSectionParseError, ManagedSectionWriteError };
|