@savvy-web/silk-core 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,87 @@
1
+ import { Effect, Schema } from "effect";
2
+
3
+ //#region src/schemas/VersioningSchemas.ts
4
+ /**
5
+ * Configuration for how private packages are handled during versioning.
6
+ *
7
+ * @remarks
8
+ * When set to `false`, private packages are completely ignored.
9
+ * When set to an object, `tag` and `version` control whether private packages
10
+ * receive git tags and version bumps respectively.
11
+ *
12
+ * @since 0.2.0
13
+ */
14
+ const PrivatePackagesConfig = Schema.Union([Schema.Struct({
15
+ tag: Schema.optional(Schema.Boolean),
16
+ version: Schema.optional(Schema.Boolean)
17
+ }), Schema.Literal(false)]);
18
+ /**
19
+ * Snapshot release configuration for changesets.
20
+ *
21
+ * @remarks
22
+ * Controls how snapshot versions are generated.
23
+ * `useCalculatedVersion` prepends the calculated version to the snapshot tag.
24
+ * `prereleaseTemplate` is a custom template string for snapshot version format.
25
+ *
26
+ * @since 0.2.0
27
+ */
28
+ const SnapshotConfig = Schema.Struct({
29
+ useCalculatedVersion: Schema.optional(Schema.Boolean),
30
+ prereleaseTemplate: Schema.optional(Schema.String)
31
+ });
32
+ /**
33
+ * Standard changesets configuration matching the `@changesets/config@4.0.0` spec.
34
+ *
35
+ * @remarks
36
+ * Represents the parsed `.changeset/config.json` file. All fields are optional
37
+ * to allow partial configs. Use {@link (SilkChangesetConfigFile:type)} when the Silk changelog
38
+ * adapter is detected.
39
+ *
40
+ * @since 0.1.0
41
+ */
42
+ /** @public */
43
+ const ChangesetConfigFile = Schema.Struct({
44
+ changelog: Schema.optional(Schema.Union([
45
+ Schema.String,
46
+ Schema.Array(Schema.Unknown),
47
+ Schema.Literal(false)
48
+ ])),
49
+ commit: Schema.optional(Schema.Union([
50
+ Schema.Boolean,
51
+ Schema.String,
52
+ Schema.Array(Schema.Unknown)
53
+ ])),
54
+ fixed: Schema.optional(Schema.Array(Schema.Array(Schema.String))),
55
+ linked: Schema.optional(Schema.Array(Schema.Array(Schema.String))),
56
+ access: Schema.optional(Schema.Literals(["public", "restricted"])),
57
+ baseBranch: Schema.optional(Schema.String),
58
+ updateInternalDependencies: Schema.optional(Schema.Literals([
59
+ "patch",
60
+ "minor",
61
+ "major"
62
+ ])),
63
+ ignore: Schema.optional(Schema.Array(Schema.String)),
64
+ privatePackages: Schema.optional(PrivatePackagesConfig),
65
+ prettier: Schema.optional(Schema.Boolean),
66
+ changedFilePatterns: Schema.optional(Schema.Array(Schema.String)),
67
+ bumpVersionsWithWorkspaceProtocolOnly: Schema.optional(Schema.Boolean),
68
+ snapshot: Schema.optional(SnapshotConfig)
69
+ });
70
+ /**
71
+ * Extended changeset config for repos using the `@savvy-web/changesets` changelog adapter.
72
+ *
73
+ * @remarks
74
+ * Extends {@link (ChangesetConfigFile:type)} with a `_isSilk` marker flag that is automatically
75
+ * set to `true`. Detected by `ChangesetConfigReader` when the `changelog` field
76
+ * references `@savvy-web/changesets`.
77
+ *
78
+ * @since 0.1.0
79
+ */
80
+ /** @public */
81
+ const SilkChangesetConfigFile = Schema.Struct({
82
+ ...ChangesetConfigFile.fields,
83
+ _isSilk: Schema.Boolean.pipe(Schema.withDecodingDefaultType(Effect.succeed(true)), Schema.withConstructorDefault(Effect.succeed(true)))
84
+ });
85
+
86
+ //#endregion
87
+ export { ChangesetConfigFile, SilkChangesetConfigFile };
@@ -0,0 +1,209 @@
1
+ import { ChangesetConfigFile, SilkChangesetConfigFile } from "./VersioningSchemas.js";
2
+ import { trimTrailingSlashes } from "../utils/TrailingSlash.js";
3
+ import { Effect, Equal, Function, Hash, Option, Schema } from "effect";
4
+ import { PublishConfig, PublishTarget, TagStyle, VersioningStrategy } from "@effected/workspaces";
5
+
6
+ //#region src/schemas/WorkspaceAnalysisSchemas.ts
7
+ const PublishProtocol = Schema.Literals(["npm", "jsr"]);
8
+ const PublishTargetShorthand = Schema.Literals([
9
+ "npm",
10
+ "github",
11
+ "jsr"
12
+ ]);
13
+ const PublishTargetObject = Schema.Struct({
14
+ protocol: PublishProtocol.pipe(Schema.withDecodingDefaultType(Effect.succeed("npm")), Schema.withConstructorDefault(Effect.succeed("npm"))),
15
+ registry: Schema.optional(Schema.String),
16
+ directory: Schema.optional(Schema.String),
17
+ access: Schema.optional(Schema.Literals(["public", "restricted"])),
18
+ provenance: Schema.optional(Schema.Boolean),
19
+ tag: Schema.optional(Schema.String)
20
+ });
21
+ /**
22
+ * Silk-extended publishConfig schema.
23
+ *
24
+ * @remarks
25
+ * Extends the base PublishConfig from `@effected/workspaces` (which covers the
26
+ * npm standard fields — access, registry, directory, tag — and, as of kit
27
+ * round 3, `linkDirectory`) with the Silk `targets` extension for
28
+ * multi-registry publishing.
29
+ *
30
+ * @since 0.2.0
31
+ * @public
32
+ */
33
+ var SilkPublishConfig = class extends PublishConfig.extend("SilkPublishConfig")({ targets: Schema.optional(Schema.Array(Schema.Union([PublishTargetShorthand, PublishTargetObject]))) }) {};
34
+ const KNOWN_REGISTRIES = {
35
+ npm: "https://registry.npmjs.org/",
36
+ github: "https://npm.pkg.github.com/",
37
+ jsr: "https://jsr.io/"
38
+ };
39
+ /**
40
+ * Compare registry URLs ignoring a trailing slash. `SilkPublishability` resolves
41
+ * targets from the bundler's `dist/prod/targets.json` binding, which writes
42
+ * registry endpoints WITHOUT a trailing slash (`https://registry.npmjs.org`),
43
+ * while `KNOWN_REGISTRIES` / `NPM_DEFAULT` use the trailing-slash form. Normalize
44
+ * both sides so `hasTarget`/`targetFor` match regardless of which form a target
45
+ * carries (binding-driven, placeholder, or access-branch fallback).
46
+ */
47
+ const sameRegistry = (a, b) => trimTrailingSlashes(a) === trimTrailingSlashes(b);
48
+ /**
49
+ * The package's declared version. `current` is absent for a member whose manifest carries no
50
+ * `version` — legal for a private package and the ordinary shape for a private monorepo root,
51
+ * which `@effected/workspaces` discovers as a member rather than rejecting (its `missingVersion`
52
+ * failure kind was retired in 0.19.0). Such a package has no version to bump, tag or stamp.
53
+ */
54
+ const WorkspaceVersion = Schema.Struct({ current: Schema.optional(Schema.String) });
55
+ /**
56
+ * A fully analyzed workspace with publish targets, versioning status,
57
+ * and release group membership.
58
+ *
59
+ * @since 0.2.0
60
+ * @public
61
+ */
62
+ var AnalyzedWorkspace = class AnalyzedWorkspace extends Schema.TaggedClass()("AnalyzedWorkspace", {
63
+ name: Schema.String,
64
+ version: WorkspaceVersion,
65
+ path: Schema.String,
66
+ root: Schema.Boolean,
67
+ publishConfig: Schema.NullOr(SilkPublishConfig),
68
+ publishable: Schema.Boolean,
69
+ targets: Schema.Array(PublishTarget),
70
+ versioned: Schema.Boolean,
71
+ tagged: Schema.Boolean,
72
+ released: Schema.Boolean,
73
+ linked: Schema.Array(Schema.suspend(() => AnalyzedWorkspace)),
74
+ fixed: Schema.Array(Schema.suspend(() => AnalyzedWorkspace))
75
+ }) {
76
+ get isRoot() {
77
+ return this.root;
78
+ }
79
+ get isPublishable() {
80
+ return this.publishable;
81
+ }
82
+ get isReleasable() {
83
+ return this.released;
84
+ }
85
+ get isFixed() {
86
+ return this.fixed.length > 0;
87
+ }
88
+ get isLinked() {
89
+ return this.linked.length > 0;
90
+ }
91
+ publishesTo(registry) {
92
+ return this.targets.some((t) => sameRegistry(t.registry, registry));
93
+ }
94
+ hasTarget(shorthand) {
95
+ const registry = KNOWN_REGISTRIES[shorthand];
96
+ return registry !== void 0 && this.publishesTo(registry);
97
+ }
98
+ targetFor(registry) {
99
+ const found = this.targets.find((t) => sameRegistry(t.registry, registry));
100
+ return found ? Option.some(found) : Option.none();
101
+ }
102
+ [Equal.symbol](that) {
103
+ if (!(that instanceof AnalyzedWorkspace)) return false;
104
+ return this.name === that.name && this.path === that.path;
105
+ }
106
+ [Hash.symbol]() {
107
+ return Hash.optimize(Hash.combine(Hash.hash(this.name), Hash.hash(this.path)));
108
+ }
109
+ toString() {
110
+ return this.version.current === void 0 ? this.name : `${this.name}@${this.version.current}`;
111
+ }
112
+ toJSON() {
113
+ return {
114
+ _tag: "AnalyzedWorkspace",
115
+ name: this.name,
116
+ version: this.version,
117
+ path: this.path,
118
+ root: this.root,
119
+ publishable: this.publishable,
120
+ targets: this.targets,
121
+ versioned: this.versioned,
122
+ tagged: this.tagged,
123
+ released: this.released
124
+ };
125
+ }
126
+ static publishable(workspaces) {
127
+ return workspaces.filter((w) => w.publishable);
128
+ }
129
+ static releasable(workspaces) {
130
+ return workspaces.filter((w) => w.released);
131
+ }
132
+ static findByName;
133
+ /** Pretty-print an AnalyzedWorkspace instance. */
134
+ static pretty;
135
+ };
136
+ AnalyzedWorkspace.findByName = Function.dual(2, (workspaces, name) => {
137
+ const found = workspaces.find((w) => w.name === name);
138
+ return found ? Option.some(found) : Option.none();
139
+ });
140
+ AnalyzedWorkspace.pretty = Schema.toFormatter(AnalyzedWorkspace);
141
+ const PackageManagerInfo = Schema.Struct({
142
+ type: Schema.Literals([
143
+ "npm",
144
+ "pnpm",
145
+ "yarn",
146
+ "bun"
147
+ ]),
148
+ version: Schema.optional(Schema.String)
149
+ });
150
+ /**
151
+ * Full workspace analysis result containing all analyzed workspaces
152
+ * and project-level configuration.
153
+ *
154
+ * @since 0.2.0
155
+ * @public
156
+ */
157
+ var WorkspaceAnalysis = class WorkspaceAnalysis extends Schema.TaggedClass()("WorkspaceAnalysis", {
158
+ root: Schema.String,
159
+ runtime: Schema.Literals(["node", "bun"]),
160
+ packageManager: PackageManagerInfo,
161
+ workspaces: Schema.Array(AnalyzedWorkspace),
162
+ changesetConfig: Schema.NullOr(Schema.Union([SilkChangesetConfigFile, ChangesetConfigFile])),
163
+ versioning: Schema.NullOr(VersioningStrategy),
164
+ tagStrategy: Schema.NullOr(TagStyle)
165
+ }) {
166
+ findWorkspace(name) {
167
+ const found = this.workspaces.find((w) => w.name === name);
168
+ return found ? Option.some(found) : Option.none();
169
+ }
170
+ get rootWorkspace() {
171
+ const root = this.workspaces.find((w) => w.root);
172
+ return root ? Option.some(root) : Option.none();
173
+ }
174
+ get publishableWorkspaces() {
175
+ return this.workspaces.filter((w) => w.publishable);
176
+ }
177
+ get versionedWorkspaces() {
178
+ return this.workspaces.filter((w) => w.versioned);
179
+ }
180
+ get taggedWorkspaces() {
181
+ return this.workspaces.filter((w) => w.tagged);
182
+ }
183
+ get releasableWorkspaces() {
184
+ return this.workspaces.filter((w) => w.released);
185
+ }
186
+ get isSilk() {
187
+ if (this.changesetConfig == null) return false;
188
+ return "_isSilk" in this.changesetConfig && this.changesetConfig._isSilk === true;
189
+ }
190
+ get hasChangesets() {
191
+ return this.changesetConfig != null;
192
+ }
193
+ [Equal.symbol](that) {
194
+ if (!(that instanceof WorkspaceAnalysis)) return false;
195
+ return this.root === that.root;
196
+ }
197
+ [Hash.symbol]() {
198
+ return Hash.optimize(Hash.hash(this.root));
199
+ }
200
+ toString() {
201
+ return `WorkspaceAnalysis(${this.root}, ${this.workspaces.length} workspaces)`;
202
+ }
203
+ /** Pretty-print a WorkspaceAnalysis instance. */
204
+ static pretty;
205
+ };
206
+ WorkspaceAnalysis.pretty = Schema.toFormatter(WorkspaceAnalysis);
207
+
208
+ //#endregion
209
+ export { AnalyzedWorkspace, SilkPublishConfig, WorkspaceAnalysis };
@@ -0,0 +1,11 @@
1
+ // This file is read by tools that parse documentation comments conforming to the TSDoc standard.
2
+ // It should be published with your NPM package. It should not be tracked by Git.
3
+ {
4
+ "tsdocVersion": "0.12",
5
+ "toolPackages": [
6
+ {
7
+ "packageName": "@microsoft/api-extractor",
8
+ "packageVersion": "7.59.1"
9
+ }
10
+ ]
11
+ }
@@ -0,0 +1,21 @@
1
+ //#region src/utils/TrailingSlash.ts
2
+ /**
3
+ * Trim trailing slashes from a string.
4
+ *
5
+ * @remarks
6
+ * Trims trailing slashes with an index scan rather than `/\/+$/`. That regex is
7
+ * unanchored at the start, so the engine retries the match from every position
8
+ * and degrades to O(n²) on a string of many slashes (CodeQL `js/polynomial-redos`).
9
+ * Only a trailing run of slashes is removed; interior slash runs are untouched.
10
+ *
11
+ * @since 0.1.0
12
+ * @public
13
+ */
14
+ const trimTrailingSlashes = (s) => {
15
+ let end = s.length;
16
+ while (end > 0 && s[end - 1] === "/") end -= 1;
17
+ return s.slice(0, end);
18
+ };
19
+
20
+ //#endregion
21
+ export { trimTrailingSlashes };