@savvy-web/silk-effects 0.4.1 → 0.5.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 +37 -2
- package/index.d.ts +88 -0
- package/index.js +195 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Shared [Effect](https://effect.website/) library providing Silk Suite convention
|
|
|
9
9
|
|
|
10
10
|
- Detect a package's publish targets from its `package.json` `publishConfig`, with multi-registry support and a changeset-ignore-aware override for `workspaces-effect`'s `PublishabilityDetector`
|
|
11
11
|
- Read changeset config through a typed accessor service that reports silk vs vanilla mode, ignore patterns and fixed groups
|
|
12
|
-
- Manage tool-owned sections in user-editable files without clobbering surrounding content
|
|
12
|
+
- Manage tool-owned sections in user-editable files without clobbering surrounding content, including ordered multi-section sync for composing several managed regions per file
|
|
13
13
|
- Discover and resolve CLI tools globally or locally with version enforcement and caching
|
|
14
14
|
- Detect versioning strategy and format git tags from changeset configuration
|
|
15
15
|
- Locate config files and keep Biome schema URLs in sync across workspaces
|
|
@@ -166,7 +166,7 @@ Manage tool-owned delimited sections inside user-editable files. Sections are bo
|
|
|
166
166
|
|
|
167
167
|
`SectionBlock` represents the content between markers. It supports `diff()`, `prepend()` and `append()` operations and uses normalized content for equality comparison.
|
|
168
168
|
|
|
169
|
-
Methods: `read`, `write`, `sync`, `check`, `isManaged` — all support dual API (data-first and data-last) for pipe composition.
|
|
169
|
+
Methods: `read`, `write`, `sync`, `syncMany`, `check`, `remove`, `isManaged` — all support dual API (data-first and data-last) for pipe composition. `sync` manages one section; `syncMany` manages several ordered sections in one file; `remove` deletes a section including its markers.
|
|
170
170
|
|
|
171
171
|
```typescript
|
|
172
172
|
import { Effect } from "effect";
|
|
@@ -192,6 +192,10 @@ await Effect.runPromise(
|
|
|
192
192
|
// Check: compare file content against expected block
|
|
193
193
|
const check = yield* ms.check(".husky/pre-commit", block);
|
|
194
194
|
// => CheckResult: Found | NotFound
|
|
195
|
+
|
|
196
|
+
// Remove: delete the section and its markers, collapsing the leftover blank line
|
|
197
|
+
const removed = yield* ms.remove(".husky/pre-commit", def);
|
|
198
|
+
// => true if a section was removed, false if none was present
|
|
195
199
|
}).pipe(
|
|
196
200
|
Effect.provide(ManagedSectionLive),
|
|
197
201
|
Effect.provide(NodeContext.layer),
|
|
@@ -207,6 +211,37 @@ const jsDef = SectionDefinition.make({ toolName: "MY-TOOL", commentStyle: "//" }
|
|
|
207
211
|
|
|
208
212
|
Use `ShellSectionDefinition` when the comment style is always `#` and should not be configurable.
|
|
209
213
|
|
|
214
|
+
`syncMany` keeps several sections in one file in their declared relative order. It updates existing sections in place, inserts a missing section next to its declared sibling, normalizes order when sections drift out of order and preserves user content and unrelated tool sections. It returns one `SyncResult` per input block in input order and is idempotent.
|
|
215
|
+
|
|
216
|
+
The `SavvySections` exports compose ordered managed sections per husky hook file. A base section defines shared shell, then each consumer layers its own one-line tool section on top:
|
|
217
|
+
|
|
218
|
+
- `SavvyBaseSection` is a `ShellSectionDefinition` (tool name `savvy-base`); pair it with `savvyBasePreamble()`, which defines `ROOT`, the `in_ci` predicate, `PM` via package-manager detection and `pm_exec`.
|
|
219
|
+
- `SavvyHooksSection` (tool name `savvy-hooks`) pairs with `savvyHooksHygiene()`, a self-guarded repo-hygiene block that runs outside CI.
|
|
220
|
+
- `savvyToolSection(toolName, command)` builds a consumer's one-line tool section whose content is exactly `in_ci || pm_exec <command>` — the command is appended verbatim, so shell tokens like `$ROOT` and `$1` survive into the output. A `savvy-base` section must precede it in the same hook file, so pass both to `syncMany` in that order.
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
import { Effect } from "effect";
|
|
224
|
+
import { NodeContext } from "@effect/platform-node";
|
|
225
|
+
import {
|
|
226
|
+
ManagedSection, ManagedSectionLive,
|
|
227
|
+
SavvyBaseSection, savvyBasePreamble, savvyToolSection,
|
|
228
|
+
} from "@savvy-web/silk-effects";
|
|
229
|
+
|
|
230
|
+
await Effect.runPromise(
|
|
231
|
+
Effect.gen(function* () {
|
|
232
|
+
const ms = yield* ManagedSection;
|
|
233
|
+
const results = yield* ms.syncMany(".husky/commit-msg", [
|
|
234
|
+
SavvyBaseSection.block(savvyBasePreamble()),
|
|
235
|
+
savvyToolSection("savvy-commit", 'commitlint --config "$ROOT/lib/configs/commitlint.config.ts" --edit "$1"'),
|
|
236
|
+
]);
|
|
237
|
+
// => ReadonlyArray<SyncResult>, one per input block in declared order
|
|
238
|
+
}).pipe(
|
|
239
|
+
Effect.provide(ManagedSectionLive),
|
|
240
|
+
Effect.provide(NodeContext.layer),
|
|
241
|
+
),
|
|
242
|
+
);
|
|
243
|
+
```
|
|
244
|
+
|
|
210
245
|
#### VersioningStrategy
|
|
211
246
|
|
|
212
247
|
Classify the versioning strategy from changeset configuration. Outputs `"single"` (0-1 publishable packages), `"fixed-group"` (all packages in one fixed group) or `"independent"` (multiple packages, not in a single group). Falls back gracefully if config is missing.
|
package/index.d.ts
CHANGED
|
@@ -626,10 +626,18 @@ declare const ManagedSection_base: Context.TagClass<ManagedSection, "@savvy-web/
|
|
|
626
626
|
(block: SectionBlock): (path: string) => Effect.Effect<SyncResult, SectionWriteError>;
|
|
627
627
|
(path: string, block: SectionBlock): Effect.Effect<SyncResult, SectionWriteError>;
|
|
628
628
|
};
|
|
629
|
+
readonly syncMany: {
|
|
630
|
+
(blocks: ReadonlyArray<SectionBlock>): (path: string) => Effect.Effect<ReadonlyArray<SyncResult>, SectionWriteError>;
|
|
631
|
+
(path: string, blocks: ReadonlyArray<SectionBlock>): Effect.Effect<ReadonlyArray<SyncResult>, SectionWriteError>;
|
|
632
|
+
};
|
|
629
633
|
readonly check: {
|
|
630
634
|
(block: SectionBlock): (path: string) => Effect.Effect<CheckResult, SectionParseError>;
|
|
631
635
|
(path: string, block: SectionBlock): Effect.Effect<CheckResult, SectionParseError>;
|
|
632
636
|
};
|
|
637
|
+
readonly remove: {
|
|
638
|
+
(definition: SectionDefinition): (path: string) => Effect.Effect<boolean, SectionWriteError>;
|
|
639
|
+
(path: string, definition: SectionDefinition): Effect.Effect<boolean, SectionWriteError>;
|
|
640
|
+
};
|
|
633
641
|
}>;
|
|
634
642
|
|
|
635
643
|
/**
|
|
@@ -796,6 +804,86 @@ declare const ResolvedTool_base: Schema.TaggedClass<ResolvedTool, "ResolvedTool"
|
|
|
796
804
|
mismatch: typeof Schema.Boolean;
|
|
797
805
|
}>;
|
|
798
806
|
|
|
807
|
+
/**
|
|
808
|
+
* Package-manager detection preamble shared across Silk Suite hook files.
|
|
809
|
+
*
|
|
810
|
+
* @remarks
|
|
811
|
+
* Side-effect-free definitions meant to run unconditionally — no markers, no outer CI
|
|
812
|
+
* guard. Defines `ROOT`, the `in_ci` predicate, `PM` (via `detect_pm`), and `pm_exec`.
|
|
813
|
+
* `pm_exec` uses local/exec semantics for every package manager and `bun x` (space form),
|
|
814
|
+
* which works regardless of how bun was installed (the `bunx` shim is not always on PATH).
|
|
815
|
+
*
|
|
816
|
+
* @returns The preamble shell, with no surrounding markers or trailing newline.
|
|
817
|
+
*
|
|
818
|
+
* @since 0.5.0
|
|
819
|
+
*/
|
|
820
|
+
export declare function savvyBasePreamble(): string;
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* Section identity for the shared package-manager preamble.
|
|
824
|
+
*
|
|
825
|
+
* `toolName` is `"savvy-base"`; pair with {@link savvyBasePreamble} to build the block:
|
|
826
|
+
*
|
|
827
|
+
* @example
|
|
828
|
+
* ```ts
|
|
829
|
+
* const block = SavvyBaseSection.block(savvyBasePreamble());
|
|
830
|
+
* ```
|
|
831
|
+
*
|
|
832
|
+
* @since 0.5.0
|
|
833
|
+
*/
|
|
834
|
+
export declare const SavvyBaseSection: ShellSectionDefinition;
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* Repo-hygiene block shared across Silk Suite hook files.
|
|
838
|
+
*
|
|
839
|
+
* @remarks
|
|
840
|
+
* Self-guarded against CI and needs no package manager: disables Git's `core.fileMode`
|
|
841
|
+
* tracking and marks tracked shell scripts executable.
|
|
842
|
+
*
|
|
843
|
+
* @returns The hygiene shell, with no surrounding markers or trailing newline.
|
|
844
|
+
*
|
|
845
|
+
* @since 0.5.0
|
|
846
|
+
*/
|
|
847
|
+
export declare function savvyHooksHygiene(): string;
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* Section identity for the shared repo-hygiene block.
|
|
851
|
+
*
|
|
852
|
+
* `toolName` is `"savvy-hooks"`; pair with {@link savvyHooksHygiene}.
|
|
853
|
+
*
|
|
854
|
+
* @since 0.5.0
|
|
855
|
+
*/
|
|
856
|
+
export declare const SavvyHooksSection: ShellSectionDefinition;
|
|
857
|
+
|
|
858
|
+
/**
|
|
859
|
+
* Build a consumer's one-line tool section so every consumer calls the shared base
|
|
860
|
+
* helpers identically.
|
|
861
|
+
*
|
|
862
|
+
* @remarks
|
|
863
|
+
* The returned block's content is exactly `in_ci || pm_exec <command>` with `command`
|
|
864
|
+
* appended verbatim — it is not parsed, quoted, or interpolated, so shell tokens like
|
|
865
|
+
* `$ROOT` and `$1` survive into the generated literal.
|
|
866
|
+
*
|
|
867
|
+
* **Precondition:** a {@link SavvyBaseSection} block must precede this section in the same
|
|
868
|
+
* hook file so `in_ci` and `pm_exec` are defined. Consumers guarantee this by passing both
|
|
869
|
+
* to {@link ManagedSection.syncMany | ManagedSection.syncMany} in order:
|
|
870
|
+
*
|
|
871
|
+
* @example
|
|
872
|
+
* ```ts
|
|
873
|
+
* yield* ManagedSection.syncMany(".husky/commit-msg", [
|
|
874
|
+
* SavvyBaseSection.block(savvyBasePreamble()),
|
|
875
|
+
* savvyToolSection("savvy-commit", 'commitlint --config "$ROOT/lib/configs/commitlint.config.ts" --edit "$1"'),
|
|
876
|
+
* ]);
|
|
877
|
+
* ```
|
|
878
|
+
*
|
|
879
|
+
* @param toolName - Section identity; also drives the marker names (uppercased).
|
|
880
|
+
* @param command - The command passed verbatim to `pm_exec`, run only outside CI.
|
|
881
|
+
* @returns A shell {@link SectionBlock} (`commentStyle: "#"`) for `toolName`.
|
|
882
|
+
*
|
|
883
|
+
* @since 0.5.0
|
|
884
|
+
*/
|
|
885
|
+
export declare function savvyToolSection(toolName: string, command: string): SectionBlock;
|
|
886
|
+
|
|
799
887
|
/**
|
|
800
888
|
* The content between managed section markers.
|
|
801
889
|
*
|
package/index.js
CHANGED
|
@@ -365,6 +365,49 @@ class ShellSectionDefinition extends Schema.TaggedClass()("ShellSectionDefinitio
|
|
|
365
365
|
return `# --- END ${this.toolName.toUpperCase()} MANAGED SECTION ---`;
|
|
366
366
|
}
|
|
367
367
|
}
|
|
368
|
+
const SavvyBaseSection = ShellSectionDefinition.make({
|
|
369
|
+
toolName: "savvy-base"
|
|
370
|
+
});
|
|
371
|
+
const SavvyHooksSection = ShellSectionDefinition.make({
|
|
372
|
+
toolName: "savvy-hooks"
|
|
373
|
+
});
|
|
374
|
+
function savvyBasePreamble() {
|
|
375
|
+
return `ROOT=$(git rev-parse --show-toplevel)
|
|
376
|
+
|
|
377
|
+
in_ci() { [ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ]; }
|
|
378
|
+
|
|
379
|
+
detect_pm() {
|
|
380
|
+
if [ -f "$ROOT/package.json" ]; then
|
|
381
|
+
pm=$(jq -r '.packageManager // empty' "$ROOT/package.json" 2>/dev/null | cut -d'@' -f1)
|
|
382
|
+
if [ -n "$pm" ]; then echo "$pm"; return; fi
|
|
383
|
+
fi
|
|
384
|
+
if [ -f "$ROOT/pnpm-lock.yaml" ]; then echo "pnpm"
|
|
385
|
+
elif [ -f "$ROOT/yarn.lock" ]; then echo "yarn"
|
|
386
|
+
elif [ -f "$ROOT/bun.lock" ]; then echo "bun"
|
|
387
|
+
else echo "npm"; fi
|
|
388
|
+
}
|
|
389
|
+
PM=$(detect_pm)
|
|
390
|
+
|
|
391
|
+
pm_exec() {
|
|
392
|
+
case "$PM" in
|
|
393
|
+
pnpm) pnpm exec "$@" ;;
|
|
394
|
+
yarn) yarn exec "$@" ;;
|
|
395
|
+
bun) bun x "$@" ;;
|
|
396
|
+
*) npx --no -- "$@" ;;
|
|
397
|
+
esac
|
|
398
|
+
}`;
|
|
399
|
+
}
|
|
400
|
+
function savvyHooksHygiene() {
|
|
401
|
+
return `if ! { [ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ]; }; then
|
|
402
|
+
git config core.fileMode false
|
|
403
|
+
git ls-files -z '*.sh' | xargs -0 chmod +x 2>/dev/null || true
|
|
404
|
+
fi`;
|
|
405
|
+
}
|
|
406
|
+
function savvyToolSection(toolName, command) {
|
|
407
|
+
return ShellSectionDefinition.make({
|
|
408
|
+
toolName
|
|
409
|
+
}).block(`in_ci || pm_exec ${command}`);
|
|
410
|
+
}
|
|
368
411
|
function ToolDefinition_to_primitive(input, hint) {
|
|
369
412
|
if ("object" !== ToolDefinition_type_of(input) || null === input) return input;
|
|
370
413
|
var prim = input[Symbol.toPrimitive];
|
|
@@ -849,6 +892,34 @@ function assembleContent(before, managed, after, toolName, commentStyle) {
|
|
|
849
892
|
const end = endMarker(toolName, commentStyle);
|
|
850
893
|
return `${before}${begin}\n${managed}\n${end}${after}`;
|
|
851
894
|
}
|
|
895
|
+
const BEGIN_MARKER_RE = /^(#|\/\/) --- BEGIN (.+?) MANAGED SECTION ---$/gm;
|
|
896
|
+
function sectionKey(toolName, commentStyle) {
|
|
897
|
+
return `${toolName.toUpperCase()}::${commentStyle}`;
|
|
898
|
+
}
|
|
899
|
+
function findAllSections(content) {
|
|
900
|
+
const results = [];
|
|
901
|
+
for (const match of content.matchAll(BEGIN_MARKER_RE)){
|
|
902
|
+
const style = match[1];
|
|
903
|
+
const name = match[2];
|
|
904
|
+
const beginStart = match.index;
|
|
905
|
+
const beginEnd = beginStart + match[0].length;
|
|
906
|
+
const end = `${style} --- END ${name} MANAGED SECTION ---`;
|
|
907
|
+
const endIdx = content.indexOf(end, beginEnd);
|
|
908
|
+
if (-1 === endIdx) continue;
|
|
909
|
+
let inner = content.slice(beginEnd, endIdx);
|
|
910
|
+
if (inner.startsWith("\n")) inner = inner.slice(1);
|
|
911
|
+
if (inner.endsWith("\n")) inner = inner.slice(0, -1);
|
|
912
|
+
results.push({
|
|
913
|
+
key: sectionKey(name, style),
|
|
914
|
+
commentStyle: style,
|
|
915
|
+
content: inner,
|
|
916
|
+
raw: content.slice(beginStart, endIdx + end.length),
|
|
917
|
+
start: beginStart,
|
|
918
|
+
end: endIdx + end.length
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
return results;
|
|
922
|
+
}
|
|
852
923
|
class ManagedSection extends Context.Tag("@savvy-web/silk-effects/ManagedSection")() {
|
|
853
924
|
}
|
|
854
925
|
const ManagedSectionLive = Layer.effect(ManagedSection, Effect.gen(function*() {
|
|
@@ -923,6 +994,107 @@ const ManagedSectionLive = Layer.effect(ManagedSection, Effect.gen(function*() {
|
|
|
923
994
|
diff: d
|
|
924
995
|
});
|
|
925
996
|
}));
|
|
997
|
+
const syncMany = Function.dual(2, (path, blocks)=>Effect.gen(function*() {
|
|
998
|
+
const exists = yield* fs.exists(path).pipe(Effect.orElseSucceed(()=>false));
|
|
999
|
+
const original = exists ? yield* fs.readFileString(path).pipe(Effect.mapError((cause)=>new SectionWriteError({
|
|
1000
|
+
path,
|
|
1001
|
+
reason: String(cause)
|
|
1002
|
+
}))) : "";
|
|
1003
|
+
const keyOf = (b)=>sectionKey(b.toolName, b.commentStyle);
|
|
1004
|
+
const found = findAllSections(original);
|
|
1005
|
+
const onDiskByKey = new Map();
|
|
1006
|
+
for (const f of found)if (!onDiskByKey.has(f.key)) onDiskByKey.set(f.key, f);
|
|
1007
|
+
const results = blocks.map((block)=>{
|
|
1008
|
+
const onDisk = onDiskByKey.get(keyOf(block));
|
|
1009
|
+
if (void 0 === onDisk) return SyncResult.Created();
|
|
1010
|
+
const current = SectionBlock.make({
|
|
1011
|
+
toolName: block.toolName,
|
|
1012
|
+
commentStyle: block.commentStyle,
|
|
1013
|
+
content: onDisk.content
|
|
1014
|
+
});
|
|
1015
|
+
if (Equal.equals(current, block)) return SyncResult.Unchanged();
|
|
1016
|
+
return SyncResult.Updated({
|
|
1017
|
+
diff: SectionBlock.diff(current, block)
|
|
1018
|
+
});
|
|
1019
|
+
});
|
|
1020
|
+
const items = [];
|
|
1021
|
+
let cursor = 0;
|
|
1022
|
+
for (const f of found){
|
|
1023
|
+
items.push({
|
|
1024
|
+
kind: "text",
|
|
1025
|
+
value: original.slice(cursor, f.start)
|
|
1026
|
+
});
|
|
1027
|
+
items.push({
|
|
1028
|
+
kind: "section",
|
|
1029
|
+
key: f.key,
|
|
1030
|
+
raw: f.raw,
|
|
1031
|
+
render: null
|
|
1032
|
+
});
|
|
1033
|
+
cursor = f.end;
|
|
1034
|
+
}
|
|
1035
|
+
items.push({
|
|
1036
|
+
kind: "text",
|
|
1037
|
+
value: original.slice(cursor)
|
|
1038
|
+
});
|
|
1039
|
+
const targetKeys = new Set(blocks.map(keyOf));
|
|
1040
|
+
const slotIndices = [];
|
|
1041
|
+
items.forEach((item, idx)=>{
|
|
1042
|
+
if ("section" === item.kind && targetKeys.has(item.key)) slotIndices.push(idx);
|
|
1043
|
+
});
|
|
1044
|
+
const itemIndexByDeclared = new Map();
|
|
1045
|
+
let slotCursor = 0;
|
|
1046
|
+
blocks.forEach((block, declaredIdx)=>{
|
|
1047
|
+
if (!onDiskByKey.has(keyOf(block))) return;
|
|
1048
|
+
if (slotCursor >= slotIndices.length) return;
|
|
1049
|
+
const itemIdx = slotIndices[slotCursor];
|
|
1050
|
+
const item = items[itemIdx];
|
|
1051
|
+
if ("section" === item.kind) item.render = block;
|
|
1052
|
+
itemIndexByDeclared.set(declaredIdx, itemIdx);
|
|
1053
|
+
slotCursor += 1;
|
|
1054
|
+
});
|
|
1055
|
+
const beforeAnchor = new Map();
|
|
1056
|
+
const afterAnchor = new Map();
|
|
1057
|
+
const appendList = [];
|
|
1058
|
+
const pushInto = (map, anchor, block)=>{
|
|
1059
|
+
const arr = map.get(anchor) ?? [];
|
|
1060
|
+
arr.push(block);
|
|
1061
|
+
map.set(anchor, arr);
|
|
1062
|
+
};
|
|
1063
|
+
blocks.forEach((block, i)=>{
|
|
1064
|
+
if (onDiskByKey.has(keyOf(block))) return;
|
|
1065
|
+
let placed = false;
|
|
1066
|
+
for(let j = i + 1; j < blocks.length && !placed; j += 1){
|
|
1067
|
+
const itemIdx = itemIndexByDeclared.get(j);
|
|
1068
|
+
if (void 0 !== itemIdx) {
|
|
1069
|
+
pushInto(beforeAnchor, itemIdx, block);
|
|
1070
|
+
placed = true;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
for(let j = i - 1; j >= 0 && !placed; j -= 1){
|
|
1074
|
+
const itemIdx = itemIndexByDeclared.get(j);
|
|
1075
|
+
if (void 0 !== itemIdx) {
|
|
1076
|
+
pushInto(afterAnchor, itemIdx, block);
|
|
1077
|
+
placed = true;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
if (!placed) appendList.push(block);
|
|
1081
|
+
});
|
|
1082
|
+
const out = [];
|
|
1083
|
+
items.forEach((item, idx)=>{
|
|
1084
|
+
if ("text" === item.kind) return void out.push(item.value);
|
|
1085
|
+
for (const block of beforeAnchor.get(idx) ?? [])out.push(block.rendered, "\n\n");
|
|
1086
|
+
out.push(null === item.render ? item.raw : item.render.rendered);
|
|
1087
|
+
for (const block of afterAnchor.get(idx) ?? [])out.push("\n\n", block.rendered);
|
|
1088
|
+
});
|
|
1089
|
+
let output = out.join("");
|
|
1090
|
+
for (const block of appendList)output = "" === output.trim() ? `${block.rendered}\n` : `${output.replace(/\n+$/, "")}\n\n${block.rendered}\n`;
|
|
1091
|
+
if ("" !== output && !output.endsWith("\n")) output += "\n";
|
|
1092
|
+
if (output !== original) yield* fs.writeFileString(path, output).pipe(Effect.mapError((cause)=>new SectionWriteError({
|
|
1093
|
+
path,
|
|
1094
|
+
reason: String(cause)
|
|
1095
|
+
})));
|
|
1096
|
+
return results;
|
|
1097
|
+
}));
|
|
926
1098
|
const check = Function.dual(2, (path, block)=>Effect.gen(function*() {
|
|
927
1099
|
const onDisk = yield* read(path, {
|
|
928
1100
|
toolName: block.toolName,
|
|
@@ -936,12 +1108,33 @@ const ManagedSectionLive = Layer.effect(ManagedSection, Effect.gen(function*() {
|
|
|
936
1108
|
diff: d
|
|
937
1109
|
});
|
|
938
1110
|
}));
|
|
1111
|
+
const remove = Function.dual(2, (path, definition)=>Effect.gen(function*() {
|
|
1112
|
+
const exists = yield* fs.exists(path).pipe(Effect.orElseSucceed(()=>false));
|
|
1113
|
+
if (!exists) return false;
|
|
1114
|
+
const raw = yield* fs.readFileString(path).pipe(Effect.mapError((cause)=>new SectionWriteError({
|
|
1115
|
+
path,
|
|
1116
|
+
reason: String(cause)
|
|
1117
|
+
})));
|
|
1118
|
+
const parsed = parseContent(raw, definition.toolName, definition.commentStyle);
|
|
1119
|
+
if (null === parsed) return false;
|
|
1120
|
+
const before = parsed.before.replace(/\n+$/, "");
|
|
1121
|
+
const after = parsed.after.replace(/^\n+/, "");
|
|
1122
|
+
let next;
|
|
1123
|
+
next = "" !== before && "" !== after ? `${before}\n\n${after}` : "" !== before ? `${before}\n` : after;
|
|
1124
|
+
yield* fs.writeFileString(path, next).pipe(Effect.mapError((cause)=>new SectionWriteError({
|
|
1125
|
+
path,
|
|
1126
|
+
reason: String(cause)
|
|
1127
|
+
})));
|
|
1128
|
+
return true;
|
|
1129
|
+
}));
|
|
939
1130
|
return {
|
|
940
1131
|
read,
|
|
941
1132
|
write,
|
|
942
1133
|
isManaged,
|
|
943
1134
|
sync,
|
|
944
|
-
|
|
1135
|
+
syncMany,
|
|
1136
|
+
check,
|
|
1137
|
+
remove
|
|
945
1138
|
};
|
|
946
1139
|
}));
|
|
947
1140
|
const NPM_DEFAULT = "https://registry.npmjs.org/";
|
|
@@ -1454,4 +1647,4 @@ const ToolDiscoveryLive = Layer.effect(ToolDiscovery, Effect.gen(function*() {
|
|
|
1454
1647
|
clearCache
|
|
1455
1648
|
};
|
|
1456
1649
|
}));
|
|
1457
|
-
export { AnalyzedWorkspace, BiomeSchemaSync, BiomeSchemaSyncLive, BiomeSyncError, ChangesetConfig, ChangesetConfigError, ChangesetConfigLive, ChangesetConfigReader, ChangesetConfigReaderLive, CheckResult, ConfigDiscovery, ConfigDiscoveryLive, ConfigNotFoundError, ManagedSection, ManagedSectionLive, PublishabilityDetectorAdaptiveLive, ResolutionPolicy, ResolvedTool, SectionBlock, SectionDefinition, SectionDiff, SectionParseError, SectionValidationError, SectionWriteError, ShellSectionDefinition, SilkPublishConfig, SilkPublishability, SilkPublishabilityDetectorLive, SilkWorkspaceAnalyzer, SilkWorkspaceAnalyzerLive, SourceRequirement, SyncResult, TagFormatError, TagStrategy, TagStrategyLive, ToolCommand, ToolDefinition, ToolDiscovery, ToolDiscoveryLive, ToolNotFoundError, ToolResolutionError, ToolSource, ToolVersionMismatchError, VersionExtractor, VersioningDetectionError, VersioningStrategy, VersioningStrategyLive, WorkspaceAnalysis, WorkspaceAnalysisError, buildSchemaUrl, extractSemver };
|
|
1650
|
+
export { AnalyzedWorkspace, BiomeSchemaSync, BiomeSchemaSyncLive, BiomeSyncError, ChangesetConfig, ChangesetConfigError, ChangesetConfigLive, ChangesetConfigReader, ChangesetConfigReaderLive, CheckResult, ConfigDiscovery, ConfigDiscoveryLive, ConfigNotFoundError, ManagedSection, ManagedSectionLive, PublishabilityDetectorAdaptiveLive, ResolutionPolicy, ResolvedTool, SavvyBaseSection, SavvyHooksSection, SectionBlock, SectionDefinition, SectionDiff, SectionParseError, SectionValidationError, SectionWriteError, ShellSectionDefinition, SilkPublishConfig, SilkPublishability, SilkPublishabilityDetectorLive, SilkWorkspaceAnalyzer, SilkWorkspaceAnalyzerLive, SourceRequirement, SyncResult, TagFormatError, TagStrategy, TagStrategyLive, ToolCommand, ToolDefinition, ToolDiscovery, ToolDiscoveryLive, ToolNotFoundError, ToolResolutionError, ToolSource, ToolVersionMismatchError, VersionExtractor, VersioningDetectionError, VersioningStrategy, VersioningStrategyLive, WorkspaceAnalysis, WorkspaceAnalysisError, buildSchemaUrl, extractSemver, savvyBasePreamble, savvyHooksHygiene, savvyToolSection };
|
package/package.json
CHANGED