@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.
@@ -0,0 +1,234 @@
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
+ * Standard changesets configuration matching the `@changesets/types` upstream spec.
11
+ *
12
+ * @remarks
13
+ * Represents the parsed `.changeset/config.json` file. All fields are optional
14
+ * to allow partial configs. Use {@link SilkChangesetConfig} when the Silk changelog
15
+ * adapter is detected.
16
+ *
17
+ * @since 0.1.0
18
+ */
19
+ export declare const ChangesetConfig: Schema.Struct<{
20
+ changelog: Schema.optional<Schema.Union<[typeof Schema.String, Schema.Array$<typeof Schema.Unknown>]>>;
21
+ commit: Schema.optional<typeof Schema.Boolean>;
22
+ fixed: Schema.optional<Schema.Array$<Schema.Array$<typeof Schema.String>>>;
23
+ linked: Schema.optional<Schema.Array$<Schema.Array$<typeof Schema.String>>>;
24
+ access: Schema.optional<Schema.Literal<["public", "restricted"]>>;
25
+ baseBranch: Schema.optional<typeof Schema.String>;
26
+ updateInternalDependencies: Schema.optional<Schema.Literal<["patch", "minor", "major"]>>;
27
+ ignore: Schema.optional<Schema.Array$<typeof Schema.String>>;
28
+ }>;
29
+
30
+ /** @since 0.1.0 */
31
+ export declare type ChangesetConfig = typeof ChangesetConfig.Type;
32
+
33
+ /**
34
+ * Raised when the `.changeset/config.json` file cannot be read or decoded.
35
+ *
36
+ * @remarks
37
+ * Returned by {@link ChangesetConfigReader.read} when the file is missing,
38
+ * contains invalid JSON, or fails Effect Schema validation.
39
+ *
40
+ * @since 0.1.0
41
+ */
42
+ export declare class ChangesetConfigError extends ChangesetConfigError_base<{
43
+ readonly path: string;
44
+ readonly reason: string;
45
+ }> {
46
+ get message(): string;
47
+ }
48
+
49
+ declare const ChangesetConfigError_base: new <A extends Record<string, any> = {}>(args: VoidIfEmpty< { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => YieldableError & {
50
+ readonly _tag: "ChangesetConfigError";
51
+ } & Readonly<A>;
52
+
53
+ /**
54
+ * Service that reads and decodes the `.changeset/config.json` for a given workspace root.
55
+ *
56
+ * @remarks
57
+ * Automatically detects whether the config uses the Silk changelog adapter
58
+ * (`@savvy-web/changesets`) and decodes as {@link SilkChangesetConfig} or the
59
+ * standard {@link ChangesetConfig} accordingly.
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * const result = await Effect.runPromise(
64
+ * Effect.gen(function* () {
65
+ * const reader = yield* ChangesetConfigReader;
66
+ * return yield* reader.read(process.cwd());
67
+ * }).pipe(
68
+ * Effect.provide(ChangesetConfigReaderLive),
69
+ * Effect.provide(NodeContext.layer),
70
+ * )
71
+ * );
72
+ * ```
73
+ *
74
+ * @since 0.1.0
75
+ */
76
+ export declare class ChangesetConfigReader extends ChangesetConfigReader_base {
77
+ }
78
+
79
+ declare const ChangesetConfigReader_base: Context.TagClass<ChangesetConfigReader, "@savvy-web/silk-effects/ChangesetConfigReader", {
80
+ /**
81
+ * Read and decode `.changeset/config.json` from the given workspace root.
82
+ *
83
+ * @param root - Absolute path to the workspace root containing the `.changeset/` directory.
84
+ * @returns An `Effect` that succeeds with the decoded config or fails with {@link ChangesetConfigError}.
85
+ *
86
+ * @since 0.1.0
87
+ */
88
+ readonly read: (root: string) => Effect.Effect<ChangesetConfig | SilkChangesetConfig, ChangesetConfigError>;
89
+ }>;
90
+
91
+ /**
92
+ * Live implementation of {@link ChangesetConfigReader}.
93
+ *
94
+ * @remarks
95
+ * Requires `FileSystem` from `@effect/platform`. Provide `NodeContext.layer` or
96
+ * `BunContext.layer` to satisfy this dependency.
97
+ *
98
+ * @since 0.1.0
99
+ */
100
+ export declare const ChangesetConfigReaderLive: Layer.Layer<ChangesetConfigReader, never, FileSystem.FileSystem>;
101
+
102
+ /**
103
+ * Extended changeset config for repos using the `@savvy-web/changesets` changelog adapter.
104
+ *
105
+ * @remarks
106
+ * Extends {@link ChangesetConfig} with a `_isSilk` marker flag that is automatically
107
+ * set to `true`. Detected by {@link ChangesetConfigReader} when the `changelog` field
108
+ * references `@savvy-web/changesets`.
109
+ *
110
+ * @since 0.1.0
111
+ */
112
+ export declare const SilkChangesetConfig: Schema.extend<Schema.Struct<{
113
+ changelog: Schema.optional<Schema.Union<[typeof Schema.String, Schema.Array$<typeof Schema.Unknown>]>>;
114
+ commit: Schema.optional<typeof Schema.Boolean>;
115
+ fixed: Schema.optional<Schema.Array$<Schema.Array$<typeof Schema.String>>>;
116
+ linked: Schema.optional<Schema.Array$<Schema.Array$<typeof Schema.String>>>;
117
+ access: Schema.optional<Schema.Literal<["public", "restricted"]>>;
118
+ baseBranch: Schema.optional<typeof Schema.String>;
119
+ updateInternalDependencies: Schema.optional<Schema.Literal<["patch", "minor", "major"]>>;
120
+ ignore: Schema.optional<Schema.Array$<typeof Schema.String>>;
121
+ }>, Schema.Struct<{
122
+ _isSilk: Schema.optionalWith<typeof Schema.Boolean, {
123
+ default: () => true;
124
+ }>;
125
+ }>>;
126
+
127
+ /** @since 0.1.0 */
128
+ export declare type SilkChangesetConfig = typeof SilkChangesetConfig.Type;
129
+
130
+ /**
131
+ * Raised when the versioning strategy cannot be determined from the workspace state.
132
+ *
133
+ * @remarks
134
+ * Returned by {@link VersioningStrategy.detect} when an unexpected condition
135
+ * prevents strategy classification.
136
+ *
137
+ * @since 0.1.0
138
+ */
139
+ export declare class VersioningDetectionError extends VersioningDetectionError_base<{
140
+ readonly reason: string;
141
+ }> {
142
+ get message(): string;
143
+ }
144
+
145
+ declare const VersioningDetectionError_base: new <A extends Record<string, any> = {}>(args: VoidIfEmpty< { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => YieldableError & {
146
+ readonly _tag: "VersioningDetectionError";
147
+ } & Readonly<A>;
148
+
149
+ /**
150
+ * Service that classifies the versioning strategy used by a workspace.
151
+ *
152
+ * @remarks
153
+ * Reads the changesets config to inspect `fixed` groups, then determines whether
154
+ * the workspace uses a single-package, fixed-group, or independent versioning strategy.
155
+ * Falls back to safe defaults when the changeset config is unavailable.
156
+ *
157
+ * @example
158
+ * ```typescript
159
+ * const result = await Effect.runPromise(
160
+ * Effect.gen(function* () {
161
+ * const strategy = yield* VersioningStrategy;
162
+ * return yield* strategy.detect(["@my-org/pkg-a", "@my-org/pkg-b"]);
163
+ * }).pipe(
164
+ * Effect.provide(VersioningStrategyLive),
165
+ * Effect.provide(ChangesetConfigReaderLive),
166
+ * Effect.provide(NodeContext.layer),
167
+ * )
168
+ * );
169
+ * ```
170
+ *
171
+ * @since 0.1.0
172
+ */
173
+ export declare class VersioningStrategy extends VersioningStrategy_base {
174
+ }
175
+
176
+ declare const VersioningStrategy_base: Context.TagClass<VersioningStrategy, "@savvy-web/silk-effects/VersioningStrategy", {
177
+ /**
178
+ * Classify the versioning strategy for a list of publishable package names.
179
+ *
180
+ * @param publishablePackages - Package names (e.g. `"@my-org/pkg"`) that will be published.
181
+ * @param root - Workspace root directory to read changeset config from.
182
+ * @returns An `Effect` resolving to a {@link VersioningStrategyResult}, or failing with
183
+ * {@link VersioningDetectionError} on unexpected errors.
184
+ *
185
+ * @since 0.1.0
186
+ */
187
+ readonly detect: (publishablePackages: ReadonlyArray<string>, root: string) => Effect.Effect<VersioningStrategyResult, VersioningDetectionError>;
188
+ }>;
189
+
190
+ /**
191
+ * Live implementation of {@link VersioningStrategy}.
192
+ *
193
+ * @remarks
194
+ * Requires {@link ChangesetConfigReader} to read the workspace changeset configuration.
195
+ * If the config file is absent, an empty `fixed` groups array is assumed.
196
+ *
197
+ * @since 0.1.0
198
+ */
199
+ export declare const VersioningStrategyLive: Layer.Layer<VersioningStrategy, never, ChangesetConfigReader>;
200
+
201
+ /**
202
+ * Output of the versioning strategy detection, combining the strategy type with group metadata.
203
+ *
204
+ * @remarks
205
+ * Produced by {@link VersioningStrategy.detect} and consumed by {@link TagStrategy.determine}
206
+ * to decide on the appropriate git-tag format.
207
+ *
208
+ * @since 0.1.0
209
+ */
210
+ export declare const VersioningStrategyResult: Schema.Struct<{
211
+ type: Schema.Literal<["single", "fixed-group", "independent"]>;
212
+ fixedGroups: Schema.Array$<Schema.Array$<typeof Schema.String>>;
213
+ publishablePackages: Schema.Array$<typeof Schema.String>;
214
+ }>;
215
+
216
+ /** @since 0.1.0 */
217
+ export declare type VersioningStrategyResult = typeof VersioningStrategyResult.Type;
218
+
219
+ /**
220
+ * Versioning strategy classification for a workspace.
221
+ *
222
+ * @remarks
223
+ * - `"single"` — one publishable package; a single version tag is used.
224
+ * - `"fixed-group"` — all publishable packages are in the same changesets fixed group.
225
+ * - `"independent"` — multiple publishable packages with independent version bumps.
226
+ *
227
+ * @since 0.1.0
228
+ */
229
+ export declare const VersioningStrategyType: Schema.Literal<["single", "fixed-group", "independent"]>;
230
+
231
+ /** @since 0.1.0 */
232
+ export declare type VersioningStrategyType = typeof VersioningStrategyType.Type;
233
+
234
+ export { }
package/versioning.js ADDED
@@ -0,0 +1,115 @@
1
+ import { FileSystem } from "@effect/platform";
2
+ import { Context, Data, Effect, Layer, Schema } from "effect";
3
+ class ChangesetConfigError extends Data.TaggedError("ChangesetConfigError") {
4
+ get message() {
5
+ return `Failed to read changeset config at ${this.path}: ${this.reason}`;
6
+ }
7
+ }
8
+ class VersioningDetectionError extends Data.TaggedError("VersioningDetectionError") {
9
+ get message() {
10
+ return `Failed to detect versioning strategy: ${this.reason}`;
11
+ }
12
+ }
13
+ const ChangesetConfig = Schema.Struct({
14
+ changelog: Schema.optional(Schema.Union(Schema.String, Schema.Array(Schema.Unknown))),
15
+ commit: Schema.optional(Schema.Boolean),
16
+ fixed: Schema.optional(Schema.Array(Schema.Array(Schema.String))),
17
+ linked: Schema.optional(Schema.Array(Schema.Array(Schema.String))),
18
+ access: Schema.optional(Schema.Literal("public", "restricted")),
19
+ baseBranch: Schema.optional(Schema.String),
20
+ updateInternalDependencies: Schema.optional(Schema.Literal("patch", "minor", "major")),
21
+ ignore: Schema.optional(Schema.Array(Schema.String))
22
+ });
23
+ const SilkChangesetConfig = Schema.extend(ChangesetConfig, Schema.Struct({
24
+ _isSilk: Schema.optionalWith(Schema.Boolean, {
25
+ default: ()=>true
26
+ })
27
+ }));
28
+ const VersioningStrategyType = Schema.Literal("single", "fixed-group", "independent");
29
+ Schema.Struct({
30
+ type: VersioningStrategyType,
31
+ fixedGroups: Schema.Array(Schema.Array(Schema.String)),
32
+ publishablePackages: Schema.Array(Schema.String)
33
+ });
34
+ const SILK_CHANGELOG_MARKER = "@savvy-web/changesets";
35
+ function isSilkChangelog(changelog) {
36
+ if ("string" == typeof changelog) return changelog.includes(SILK_CHANGELOG_MARKER);
37
+ if (Array.isArray(changelog) && changelog.length > 0) return "string" == typeof changelog[0] && changelog[0].includes(SILK_CHANGELOG_MARKER);
38
+ return false;
39
+ }
40
+ class ChangesetConfigReader extends Context.Tag("@savvy-web/silk-effects/ChangesetConfigReader")() {
41
+ }
42
+ const ChangesetConfigReaderLive = Layer.effect(ChangesetConfigReader, Effect.gen(function*() {
43
+ const fs = yield* FileSystem.FileSystem;
44
+ const read = (root)=>{
45
+ const configPath = `${root}/.changeset/config.json`;
46
+ return Effect.gen(function*() {
47
+ const exists = yield* fs.exists(configPath).pipe(Effect.mapError((cause)=>new ChangesetConfigError({
48
+ path: configPath,
49
+ reason: String(cause)
50
+ })));
51
+ if (!exists) return yield* Effect.fail(new ChangesetConfigError({
52
+ path: configPath,
53
+ reason: "File not found"
54
+ }));
55
+ const raw = yield* fs.readFileString(configPath).pipe(Effect.mapError((cause)=>new ChangesetConfigError({
56
+ path: configPath,
57
+ reason: String(cause)
58
+ })));
59
+ const parsed = yield* Effect["try"]({
60
+ try: ()=>JSON.parse(raw),
61
+ catch: (cause)=>new ChangesetConfigError({
62
+ path: configPath,
63
+ reason: `Invalid JSON: ${String(cause)}`
64
+ })
65
+ });
66
+ const rawConfig = parsed;
67
+ if (isSilkChangelog(rawConfig.changelog)) return yield* Schema.decodeUnknown(SilkChangesetConfig)(parsed).pipe(Effect.mapError((cause)=>new ChangesetConfigError({
68
+ path: configPath,
69
+ reason: `Schema decode failed: ${String(cause)}`
70
+ })));
71
+ return yield* Schema.decodeUnknown(ChangesetConfig)(parsed).pipe(Effect.mapError((cause)=>new ChangesetConfigError({
72
+ path: configPath,
73
+ reason: `Schema decode failed: ${String(cause)}`
74
+ })));
75
+ });
76
+ };
77
+ return {
78
+ read
79
+ };
80
+ }));
81
+ class VersioningStrategy extends Context.Tag("@savvy-web/silk-effects/VersioningStrategy")() {
82
+ }
83
+ const VersioningStrategyLive = Layer.effect(VersioningStrategy, Effect.gen(function*() {
84
+ const configReader = yield* ChangesetConfigReader;
85
+ const detect = (publishablePackages, root)=>Effect.gen(function*() {
86
+ const config = yield* configReader.read(root).pipe(Effect.orElseSucceed(()=>({
87
+ fixed: [],
88
+ linked: []
89
+ })));
90
+ const fixed = config.fixed ?? [];
91
+ const packages = [
92
+ ...publishablePackages
93
+ ];
94
+ if (packages.length <= 1) return {
95
+ type: "single",
96
+ fixedGroups: fixed,
97
+ publishablePackages: packages
98
+ };
99
+ const containingGroup = fixed.find((group)=>packages.every((pkg)=>group.includes(pkg)));
100
+ if (void 0 !== containingGroup) return {
101
+ type: "fixed-group",
102
+ fixedGroups: fixed,
103
+ publishablePackages: packages
104
+ };
105
+ return {
106
+ type: "independent",
107
+ fixedGroups: fixed,
108
+ publishablePackages: packages
109
+ };
110
+ });
111
+ return {
112
+ detect
113
+ };
114
+ }));
115
+ export { ChangesetConfigError, ChangesetConfigReader, ChangesetConfigReaderLive, VersioningDetectionError, VersioningStrategy, VersioningStrategyLive };