@savvy-web/silk-effects 7.5.3 → 8.0.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.
@@ -1,194 +0,0 @@
1
- import { CommentStyle, SectionId } from "@effected/templates";
2
-
3
- //#region src/schemas/SavvySections.ts
4
- /**
5
- * Build a shell-hook section identity from a Silk tool name.
6
- *
7
- * @remarks
8
- * **The key is uppercased here, and that is load-bearing.** The kit renders a
9
- * key verbatim into its markers (`# --- BEGIN <key> MANAGED SECTION ---`),
10
- * while the section model this replaces uppercased `toolName` on the way in. A
11
- * lowercase key would therefore emit `# --- BEGIN savvy-base MANAGED SECTION ---`
12
- * and no longer match the `SAVVY-BASE` markers already written into every
13
- * consumer repo's hook files — `check` would report the section absent and
14
- * `sync` would append a second copy beside the first. Uppercasing keeps the
15
- * marker bytes identical across the migration.
16
- */
17
- const shellSection = (toolName) => SectionId.make({
18
- key: toolName.toUpperCase(),
19
- commentStyle: CommentStyle.hash
20
- });
21
- /**
22
- * Section identity for the shared package-manager preamble.
23
- *
24
- * `toolName` is `"savvy-base"`; pair with {@link savvyBasePreamble} to build the block:
25
- *
26
- * @example
27
- * ```ts
28
- * const section = SavvyBaseSection.section(savvyBasePreamble());
29
- * ```
30
- *
31
- * @since 0.5.0
32
- * @public
33
- */
34
- const SavvyBaseSection = shellSection("savvy-base");
35
- /**
36
- * Section identity for the shared repo-hygiene block.
37
- *
38
- * `toolName` is `"savvy-hooks"`; pair with {@link savvyHooksHygiene}.
39
- *
40
- * @since 0.5.0
41
- * @public
42
- */
43
- const SavvyHooksSection = shellSection("savvy-hooks");
44
- /**
45
- * Package-manager detection preamble shared across Silk Suite hook files.
46
- *
47
- * @remarks
48
- * Side-effect-free definitions meant to run unconditionally — no markers, no outer CI
49
- * guard. Defines `ROOT`, the `in_ci` predicate, `PM` (via `detect_pm`), and `pm_exec`.
50
- * `pm_exec` uses local/exec semantics for every package manager and `bun x` (space form),
51
- * which works regardless of how bun was installed (the `bunx` shim is not always on PATH).
52
- *
53
- * @returns The preamble shell, with no surrounding markers or trailing newline.
54
- *
55
- * @since 0.5.0
56
- * @public
57
- */
58
- function savvyBasePreamble() {
59
- return `ROOT=$(git rev-parse --show-toplevel)
60
-
61
- in_ci() { [ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ]; }
62
-
63
- detect_pm() {
64
- if [ -f "$ROOT/package.json" ]; then
65
- pm=$(jq -r '.packageManager // empty' "$ROOT/package.json" 2>/dev/null | cut -d'@' -f1)
66
- if [ -n "$pm" ]; then echo "$pm"; return; fi
67
- fi
68
- if [ -f "$ROOT/pnpm-lock.yaml" ]; then echo "pnpm"
69
- elif [ -f "$ROOT/yarn.lock" ]; then echo "yarn"
70
- elif [ -f "$ROOT/bun.lock" ]; then echo "bun"
71
- else echo "npm"; fi
72
- }
73
- PM=$(detect_pm)
74
-
75
- pm_exec() {
76
- case "$PM" in
77
- pnpm) pnpm exec "$@" ;;
78
- yarn) yarn exec "$@" ;;
79
- bun) bun x "$@" ;;
80
- *) npx --no -- "$@" ;;
81
- esac
82
- }`;
83
- }
84
- /**
85
- * Repo-hygiene block shared across Silk Suite hook files.
86
- *
87
- * @remarks
88
- * Self-guarded against CI and needs no package manager: disables Git's `core.fileMode`
89
- * tracking and marks tracked shell scripts executable.
90
- *
91
- * @returns The hygiene shell, with no surrounding markers or trailing newline.
92
- *
93
- * @since 0.5.0
94
- * @public
95
- */
96
- function savvyHooksHygiene() {
97
- return `if ! { [ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ]; }; then
98
- git config core.fileMode false
99
- git ls-files -z '*.sh' | xargs -0 chmod +x 2>/dev/null || true
100
- fi`;
101
- }
102
- /**
103
- * Build a consumer's one-line tool section so every consumer calls the shared base
104
- * helpers identically.
105
- *
106
- * @remarks
107
- * The returned block's content is exactly `in_ci || pm_exec <command>` with `command`
108
- * appended verbatim — it is not parsed, quoted, or interpolated, so shell tokens like
109
- * `$ROOT` and `$1` survive into the generated literal.
110
- *
111
- * **Precondition:** a {@link SavvyBaseSection} block must precede this section in the same
112
- * hook file so `in_ci` and `pm_exec` are defined. Consumers guarantee this by passing both
113
- * to `ManagedSection.syncAll` in order:
114
- *
115
- * @example
116
- * ```ts
117
- * yield* sections.syncAll(".husky/commit-msg", [
118
- * SavvyBaseSection.section(savvyBasePreamble()),
119
- * savvyToolSection("savvy-commit", 'commitlint --config "$ROOT/lib/configs/commitlint.config.ts" --edit "$1"'),
120
- * ]);
121
- * ```
122
- *
123
- * @param toolName - Section identity; also drives the marker names (uppercased).
124
- * @param command - The command passed verbatim to `pm_exec`, run only outside CI.
125
- * @returns A shell `Section` (`commentStyle: hash`) for `toolName`.
126
- *
127
- * @since 0.5.0
128
- * @public
129
- */
130
- function savvyToolSection(toolName, command) {
131
- return shellSection(toolName).section(`in_ci || pm_exec ${command}`);
132
- }
133
- /**
134
- * Section identity for the package-manager toolchain drift check.
135
- *
136
- * `toolName` is `"savvy-toolchain"`; pair with {@link savvyToolchainCheck}.
137
- *
138
- * @since 7.3.0
139
- * @public
140
- */
141
- const SavvyToolchainSection = shellSection("savvy-toolchain");
142
- /**
143
- * Package-manager drift check shared across Silk Suite hook files.
144
- *
145
- * @remarks
146
- * Compares the running package manager's version against the repo's
147
- * `devEngines.packageManager` pin and prints a warning on mismatch. **Warn only** —
148
- * it never blocks the hook and never installs anything, so nobody mid-bisect or
149
- * mid-rebase on an older pin is stranded.
150
- *
151
- * Deliberately self-contained: its homes are `.husky/post-checkout` and
152
- * `.husky/post-merge`, which carry {@link SavvyHooksSection} but no
153
- * {@link SavvyBaseSection}, so it defines its own root/CI/pin lookups rather than
154
- * depending on `ROOT`, `in_ci` or `PM`. It honours the `name` recorded in the pin
155
- * rather than assuming pnpm.
156
- *
157
- * Every input is treated as optional: no `git` root, no `jq`, no `devEngines` block,
158
- * or a package manager that is not on `PATH` all mean "say nothing". Only an exact
159
- * pin is comparable, so ranges (`^1.2.3`, `>=1 || <2`) and wildcards (`1.x`) are
160
- * skipped, and the `+sha512…` integrity tail `devEngines` versions routinely carry is
161
- * stripped before comparison. Skipped under CI, where the runtime action installs the
162
- * pin by construction.
163
- *
164
- * @returns The drift-check shell, with no surrounding markers or trailing newline.
165
- *
166
- * @since 7.3.0
167
- * @public
168
- */
169
- function savvyToolchainCheck() {
170
- return `if ! { [ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ]; }; then
171
- toolchain_root=$(git rev-parse --show-toplevel 2>/dev/null)
172
- toolchain_pm=""
173
- toolchain_pin=""
174
- if [ -n "$toolchain_root" ] && [ -f "$toolchain_root/package.json" ] && command -v jq >/dev/null 2>&1; then
175
- toolchain_pm=$(jq -r '.devEngines.packageManager.name // empty' "$toolchain_root/package.json" 2>/dev/null)
176
- toolchain_pin=$(jq -r '.devEngines.packageManager.version // empty' "$toolchain_root/package.json" 2>/dev/null | cut -d'+' -f1)
177
- fi
178
- # Only an exact pin is comparable: drop ranges (^ ~ >= ||) and wildcards (x, *).
179
- case "$toolchain_pin" in ""|[!0-9]*|*[!0-9A-Za-z.-]*|*x*|*X*) toolchain_pin="" ;; esac
180
- if [ -n "$toolchain_pm" ] && [ -n "$toolchain_pin" ] && command -v "$toolchain_pm" >/dev/null 2>&1; then
181
- toolchain_have=$("$toolchain_pm" --version 2>/dev/null | head -n 1 | tr -d '[:space:]')
182
- # A manager that failed or answered with prose says nothing about drift.
183
- case "$toolchain_have" in [!0-9]*|*[!0-9A-Za-z.-]*) toolchain_have="" ;; esac
184
- if [ -n "$toolchain_have" ] && [ "$toolchain_have" != "$toolchain_pin" ]; then
185
- printf '⚠ %s %s does not match %s, the version pinned in devEngines.packageManager.\\n' "$toolchain_pm" "$toolchain_have" "$toolchain_pin" >&2
186
- printf ' Lockfiles written by this version may differ from CI. Fix: corepack use %s@%s\\n' "$toolchain_pm" "$toolchain_pin" >&2
187
- fi
188
- fi
189
- unset toolchain_root toolchain_pm toolchain_pin toolchain_have
190
- fi`;
191
- }
192
-
193
- //#endregion
194
- export { SavvyBaseSection, SavvyHooksSection, SavvyToolchainSection, savvyBasePreamble, savvyHooksHygiene, savvyToolSection, savvyToolchainCheck };
@@ -1,87 +0,0 @@
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 {@link 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 };
@@ -1,209 +0,0 @@
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 };
@@ -1,18 +0,0 @@
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
- const trimTrailingSlashes = (s) => {
12
- let end = s.length;
13
- while (end > 0 && s[end - 1] === "/") end -= 1;
14
- return s.slice(0, end);
15
- };
16
-
17
- //#endregion
18
- export { trimTrailingSlashes };