@savvy-web/silk-effects 0.6.0 → 1.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.
- package/README.md +48 -17
- package/_virtual/_rolldown/runtime.js +18 -0
- package/changesets/api/categories.js +247 -0
- package/changesets/api/changelog.js +134 -0
- package/changesets/api/dependency-table.js +163 -0
- package/changesets/api/linter.js +168 -0
- package/changesets/api/transformer.js +140 -0
- package/changesets/categories/index.js +299 -0
- package/changesets/categories/types.js +66 -0
- package/changesets/changelog/formatting.js +119 -0
- package/changesets/changelog/getDependencyReleaseLine.js +114 -0
- package/changesets/changelog/getReleaseLine.js +122 -0
- package/changesets/changelog/index.js +99 -0
- package/changesets/constants.js +43 -0
- package/changesets/errors.js +305 -0
- package/changesets/index.js +146 -0
- package/changesets/markdownlint/index.js +29 -0
- package/changesets/markdownlint/rules/content-structure.js +98 -0
- package/changesets/markdownlint/rules/dependency-table-format.js +170 -0
- package/changesets/markdownlint/rules/heading-hierarchy.js +61 -0
- package/changesets/markdownlint/rules/required-sections.js +54 -0
- package/changesets/markdownlint/rules/uncategorized-content.js +54 -0
- package/changesets/markdownlint/rules/utils.js +30 -0
- package/changesets/remark/plugins/aggregate-dependency-tables.js +47 -0
- package/changesets/remark/plugins/contributor-footnotes.js +123 -0
- package/changesets/remark/plugins/deduplicate-items.js +30 -0
- package/changesets/remark/plugins/issue-link-refs.js +58 -0
- package/changesets/remark/plugins/merge-sections.js +43 -0
- package/changesets/remark/plugins/normalize-format.js +47 -0
- package/changesets/remark/plugins/reorder-sections.js +34 -0
- package/changesets/remark/presets.js +119 -0
- package/changesets/remark/rules/content-structure.js +22 -0
- package/changesets/remark/rules/dependency-table-format.js +40 -0
- package/changesets/remark/rules/heading-hierarchy.js +19 -0
- package/changesets/remark/rules/required-sections.js +17 -0
- package/changesets/remark/rules/uncategorized-content.js +31 -0
- package/changesets/schemas/changeset.js +146 -0
- package/changesets/schemas/dependency-table.js +189 -0
- package/changesets/schemas/git.js +69 -0
- package/changesets/schemas/github.js +175 -0
- package/changesets/schemas/options.js +182 -0
- package/changesets/schemas/package-scope.js +128 -0
- package/changesets/schemas/primitives.js +72 -0
- package/changesets/schemas/version-files.js +151 -0
- package/changesets/services/branch-analyzer.js +278 -0
- package/changesets/services/changelog.js +50 -0
- package/changesets/services/config-inspector.js +390 -0
- package/changesets/services/github.js +178 -0
- package/changesets/services/markdown.js +106 -0
- package/changesets/services/workspace-snapshot.js +182 -0
- package/changesets/utils/commit-parser.js +80 -0
- package/changesets/utils/dep-diff.js +77 -0
- package/changesets/utils/dependency-table.js +347 -0
- package/changesets/utils/issue-refs.js +101 -0
- package/changesets/utils/jsonpath.js +175 -0
- package/changesets/utils/logger.js +50 -0
- package/changesets/utils/markdown-link.js +57 -0
- package/changesets/utils/publishability.js +39 -0
- package/changesets/utils/remark-pipeline.js +79 -0
- package/changesets/utils/section-parser.js +94 -0
- package/changesets/utils/strip-frontmatter.js +46 -0
- package/changesets/utils/version-blocks.js +108 -0
- package/changesets/utils/version-files.js +336 -0
- package/changesets/utils/worktree-snapshot.js +142 -0
- package/changesets/vendor/github-info.js +55 -0
- package/commitlint/config/factory.js +69 -0
- package/commitlint/config/plugins.js +227 -0
- package/commitlint/config/rules.js +155 -0
- package/commitlint/config/schema.js +46 -0
- package/commitlint/detection/dco.js +53 -0
- package/commitlint/detection/scopes.js +45 -0
- package/commitlint/formatter/format.js +85 -0
- package/commitlint/formatter/messages.js +79 -0
- package/commitlint/hook/diagnostics/branch.js +36 -0
- package/commitlint/hook/diagnostics/cache.js +37 -0
- package/commitlint/hook/diagnostics/commitlint-config.js +36 -0
- package/commitlint/hook/diagnostics/open-issues.js +56 -0
- package/commitlint/hook/diagnostics/package-manager.js +51 -0
- package/commitlint/hook/diagnostics/signing.js +107 -0
- package/commitlint/hook/envelope.js +46 -0
- package/commitlint/hook/output.js +45 -0
- package/commitlint/hook/parse-bash-command.js +105 -0
- package/commitlint/hook/rules/closes-trailer.js +31 -0
- package/commitlint/hook/rules/forbidden-content.js +32 -0
- package/commitlint/hook/rules/plan-leakage.js +36 -0
- package/commitlint/hook/rules/signing-flag-conflict.js +25 -0
- package/commitlint/hook/rules/soft-wrap.js +37 -0
- package/commitlint/hook/rules/types.js +14 -0
- package/commitlint/hook/rules/verbosity.js +31 -0
- package/commitlint/hook/silence-logger.js +39 -0
- package/commitlint/index.js +146 -0
- package/commitlint/prompt/config.js +91 -0
- package/commitlint/prompt/emojis.js +74 -0
- package/commitlint/prompt/prompter.js +135 -0
- package/commitlint/static.js +73 -0
- package/errors/BiomeSyncError.js +21 -0
- package/errors/ChangesetConfigError.js +20 -0
- package/errors/ConfigNotFoundError.js +21 -0
- package/errors/SectionParseError.js +16 -0
- package/errors/SectionValidationError.js +16 -0
- package/errors/SectionWriteError.js +16 -0
- package/errors/TagFormatError.js +20 -0
- package/errors/ToolNotFoundError.js +11 -0
- package/errors/ToolResolutionError.js +11 -0
- package/errors/ToolVersionMismatchError.js +11 -0
- package/errors/VersioningDetectionError.js +20 -0
- package/errors/WorkspaceAnalysisError.js +21 -0
- package/index.d.ts +9743 -8380
- package/index.js +36 -6657
- package/lint/Handler.js +39 -0
- package/lint/cli/sections.js +65 -0
- package/lint/cli/templates/markdownlint.gen.js +183 -0
- package/lint/config/Preset.js +152 -0
- package/lint/config/createConfig.js +89 -0
- package/lint/handlers/Biome.js +179 -0
- package/lint/handlers/Markdown.js +139 -0
- package/lint/handlers/PackageJson.js +130 -0
- package/lint/handlers/PnpmWorkspace.js +141 -0
- package/lint/handlers/ShellScripts.js +58 -0
- package/lint/handlers/TypeScript.js +134 -0
- package/lint/handlers/Yaml.js +167 -0
- package/lint/index.js +52 -0
- package/lint/utils/Command.js +285 -0
- package/lint/utils/Filter.js +100 -0
- package/lint/utils/Workspace.js +86 -0
- package/package.json +52 -63
- package/schemas/CommentStyle.js +16 -0
- package/schemas/ResolvedTool.js +63 -0
- package/schemas/SavvySections.js +113 -0
- package/schemas/SectionBlock.js +70 -0
- package/schemas/SectionDefinition.js +121 -0
- package/schemas/SectionResults.js +12 -0
- package/schemas/TagStrategySchemas.js +18 -0
- package/schemas/ToolDefinition.js +39 -0
- package/schemas/ToolResults.js +14 -0
- package/schemas/VersioningSchemas.js +95 -0
- package/schemas/WorkspaceAnalysisSchemas.js +190 -0
- package/services/BiomeSchemaSync.js +133 -0
- package/services/ChangesetConfig.js +78 -0
- package/services/ChangesetConfigReader.js +106 -0
- package/services/ConfigDiscovery.js +71 -0
- package/services/ManagedSection.js +288 -0
- package/services/SilkPublishability.js +193 -0
- package/services/SilkWorkspaceAnalyzer.js +213 -0
- package/services/TagStrategy.js +54 -0
- package/services/ToolDiscovery.js +229 -0
- package/services/VersioningStrategy.js +67 -0
- package/tsdoc-metadata.json +11 -11
- package/turbo/digest.js +127 -0
- package/turbo/errors.js +48 -0
- package/turbo/index.js +32 -0
- package/turbo/schemas/DryRun.js +57 -0
- package/turbo/schemas/results.js +61 -0
- package/turbo/services/TurboInspector.js +100 -0
- package/utils/ToolCommand.js +40 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { 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@3.1.1` spec.
|
|
34
|
+
*
|
|
35
|
+
* @remarks
|
|
36
|
+
* Represents the parsed `.changeset/config.json` file. All fields are optional
|
|
37
|
+
* to allow partial configs. Use {@link SilkChangesetConfigFile} when the Silk changelog
|
|
38
|
+
* adapter is detected.
|
|
39
|
+
*
|
|
40
|
+
* @since 0.1.0
|
|
41
|
+
*/
|
|
42
|
+
const ChangesetConfigFile = Schema.Struct({
|
|
43
|
+
changelog: Schema.optional(Schema.Union(Schema.String, Schema.Array(Schema.Unknown), Schema.Literal(false))),
|
|
44
|
+
commit: Schema.optional(Schema.Union(Schema.Boolean, Schema.String, Schema.Array(Schema.Unknown))),
|
|
45
|
+
fixed: Schema.optional(Schema.Array(Schema.Array(Schema.String))),
|
|
46
|
+
linked: Schema.optional(Schema.Array(Schema.Array(Schema.String))),
|
|
47
|
+
access: Schema.optional(Schema.Literal("public", "restricted")),
|
|
48
|
+
baseBranch: Schema.optional(Schema.String),
|
|
49
|
+
updateInternalDependencies: Schema.optional(Schema.Literal("patch", "minor", "major")),
|
|
50
|
+
ignore: Schema.optional(Schema.Array(Schema.String)),
|
|
51
|
+
privatePackages: Schema.optional(PrivatePackagesConfig),
|
|
52
|
+
prettier: Schema.optional(Schema.Boolean),
|
|
53
|
+
changedFilePatterns: Schema.optional(Schema.Array(Schema.String)),
|
|
54
|
+
bumpVersionsWithWorkspaceProtocolOnly: Schema.optional(Schema.Boolean),
|
|
55
|
+
snapshot: Schema.optional(SnapshotConfig)
|
|
56
|
+
});
|
|
57
|
+
/**
|
|
58
|
+
* Extended changeset config for repos using the `@savvy-web/changesets` changelog adapter.
|
|
59
|
+
*
|
|
60
|
+
* @remarks
|
|
61
|
+
* Extends {@link ChangesetConfigFile} with a `_isSilk` marker flag that is automatically
|
|
62
|
+
* set to `true`. Detected by {@link ChangesetConfigReader} when the `changelog` field
|
|
63
|
+
* references `@savvy-web/changesets`.
|
|
64
|
+
*
|
|
65
|
+
* @since 0.1.0
|
|
66
|
+
*/
|
|
67
|
+
const SilkChangesetConfigFile = Schema.extend(ChangesetConfigFile, Schema.Struct({ _isSilk: Schema.optionalWith(Schema.Boolean, { default: () => true }) }));
|
|
68
|
+
/**
|
|
69
|
+
* Versioning strategy classification for a workspace.
|
|
70
|
+
*
|
|
71
|
+
* @remarks
|
|
72
|
+
* - `"single"` — one publishable package; a single version tag is used.
|
|
73
|
+
* - `"fixed-group"` — all publishable packages are in the same changesets fixed group.
|
|
74
|
+
* - `"independent"` — multiple publishable packages with independent version bumps.
|
|
75
|
+
*
|
|
76
|
+
* @since 0.1.0
|
|
77
|
+
*/
|
|
78
|
+
const VersioningStrategyType = Schema.Literal("single", "fixed-group", "independent");
|
|
79
|
+
/**
|
|
80
|
+
* Output of the versioning strategy detection, combining the strategy type with group metadata.
|
|
81
|
+
*
|
|
82
|
+
* @remarks
|
|
83
|
+
* Produced by {@link VersioningStrategy.detect} and consumed by {@link TagStrategy.determine}
|
|
84
|
+
* to decide on the appropriate git-tag format.
|
|
85
|
+
*
|
|
86
|
+
* @since 0.1.0
|
|
87
|
+
*/
|
|
88
|
+
const VersioningStrategyResult = Schema.Struct({
|
|
89
|
+
type: VersioningStrategyType,
|
|
90
|
+
fixedGroups: Schema.Array(Schema.Array(Schema.String)),
|
|
91
|
+
publishablePackages: Schema.Array(Schema.String)
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
//#endregion
|
|
95
|
+
export { ChangesetConfigFile, SilkChangesetConfigFile, VersioningStrategyResult };
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { ChangesetConfigFile, SilkChangesetConfigFile, VersioningStrategyResult } from "./VersioningSchemas.js";
|
|
2
|
+
import { TagStrategyType } from "./TagStrategySchemas.js";
|
|
3
|
+
import { Equal, Function, Hash, Option, Pretty, Schema } from "effect";
|
|
4
|
+
import { PublishConfig, PublishTarget } from "workspaces-effect";
|
|
5
|
+
|
|
6
|
+
//#region src/schemas/WorkspaceAnalysisSchemas.ts
|
|
7
|
+
const PublishProtocol = Schema.Literal("npm", "jsr");
|
|
8
|
+
const PublishTargetShorthand = Schema.Literal("npm", "github", "jsr");
|
|
9
|
+
const PublishTargetObject = Schema.Struct({
|
|
10
|
+
protocol: Schema.optionalWith(PublishProtocol, { default: () => "npm" }),
|
|
11
|
+
registry: Schema.optional(Schema.String),
|
|
12
|
+
directory: Schema.optional(Schema.String),
|
|
13
|
+
access: Schema.optional(Schema.Literal("public", "restricted")),
|
|
14
|
+
provenance: Schema.optional(Schema.Boolean),
|
|
15
|
+
tag: Schema.optional(Schema.String)
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* Silk-extended publishConfig schema.
|
|
19
|
+
*
|
|
20
|
+
* @remarks
|
|
21
|
+
* Extends the base PublishConfig from workspaces-effect (which covers npm
|
|
22
|
+
* standard fields: access, registry, directory, tag, linkDirectory) with the
|
|
23
|
+
* Silk `targets` extension for multi-registry publishing.
|
|
24
|
+
*
|
|
25
|
+
* @since 0.2.0
|
|
26
|
+
*/
|
|
27
|
+
var SilkPublishConfig = class extends PublishConfig.extend("SilkPublishConfig")({ targets: Schema.optional(Schema.Array(Schema.Union(PublishTargetShorthand, PublishTargetObject))) }) {};
|
|
28
|
+
const KNOWN_REGISTRIES = {
|
|
29
|
+
npm: "https://registry.npmjs.org/",
|
|
30
|
+
github: "https://npm.pkg.github.com/",
|
|
31
|
+
jsr: "https://jsr.io/"
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Compare registry URLs ignoring a trailing slash. `SilkPublishability` resolves
|
|
35
|
+
* targets from the bundler's `dist/prod/targets.json` binding, which writes
|
|
36
|
+
* registry endpoints WITHOUT a trailing slash (`https://registry.npmjs.org`),
|
|
37
|
+
* while `KNOWN_REGISTRIES` / `NPM_DEFAULT` use the trailing-slash form. Normalize
|
|
38
|
+
* both sides so `hasTarget`/`targetFor` match regardless of which form a target
|
|
39
|
+
* carries (binding-driven, placeholder, or access-branch fallback).
|
|
40
|
+
*/
|
|
41
|
+
const sameRegistry = (a, b) => a.replace(/\/+$/, "") === b.replace(/\/+$/, "");
|
|
42
|
+
const WorkspaceVersion = Schema.Struct({ current: Schema.String });
|
|
43
|
+
/**
|
|
44
|
+
* A fully analyzed workspace with publish targets, versioning status,
|
|
45
|
+
* and release group membership.
|
|
46
|
+
*
|
|
47
|
+
* @since 0.2.0
|
|
48
|
+
*/
|
|
49
|
+
var AnalyzedWorkspace = class AnalyzedWorkspace extends Schema.TaggedClass()("AnalyzedWorkspace", {
|
|
50
|
+
name: Schema.String,
|
|
51
|
+
version: WorkspaceVersion,
|
|
52
|
+
path: Schema.String,
|
|
53
|
+
root: Schema.Boolean,
|
|
54
|
+
publishConfig: Schema.NullOr(SilkPublishConfig),
|
|
55
|
+
publishable: Schema.Boolean,
|
|
56
|
+
targets: Schema.Array(PublishTarget),
|
|
57
|
+
versioned: Schema.Boolean,
|
|
58
|
+
tagged: Schema.Boolean,
|
|
59
|
+
released: Schema.Boolean,
|
|
60
|
+
linked: Schema.Array(Schema.suspend(() => AnalyzedWorkspace)),
|
|
61
|
+
fixed: Schema.Array(Schema.suspend(() => AnalyzedWorkspace))
|
|
62
|
+
}) {
|
|
63
|
+
get isRoot() {
|
|
64
|
+
return this.root;
|
|
65
|
+
}
|
|
66
|
+
get isPublishable() {
|
|
67
|
+
return this.publishable;
|
|
68
|
+
}
|
|
69
|
+
get isReleasable() {
|
|
70
|
+
return this.released;
|
|
71
|
+
}
|
|
72
|
+
get isFixed() {
|
|
73
|
+
return this.fixed.length > 0;
|
|
74
|
+
}
|
|
75
|
+
get isLinked() {
|
|
76
|
+
return this.linked.length > 0;
|
|
77
|
+
}
|
|
78
|
+
publishesTo(registry) {
|
|
79
|
+
return this.targets.some((t) => sameRegistry(t.registry, registry));
|
|
80
|
+
}
|
|
81
|
+
hasTarget(shorthand) {
|
|
82
|
+
const registry = KNOWN_REGISTRIES[shorthand];
|
|
83
|
+
return registry !== void 0 && this.publishesTo(registry);
|
|
84
|
+
}
|
|
85
|
+
targetFor(registry) {
|
|
86
|
+
const found = this.targets.find((t) => sameRegistry(t.registry, registry));
|
|
87
|
+
return found ? Option.some(found) : Option.none();
|
|
88
|
+
}
|
|
89
|
+
[Equal.symbol](that) {
|
|
90
|
+
if (!(that instanceof AnalyzedWorkspace)) return false;
|
|
91
|
+
return this.name === that.name && this.path === that.path;
|
|
92
|
+
}
|
|
93
|
+
[Hash.symbol]() {
|
|
94
|
+
return Hash.cached(this)(Hash.combine(Hash.hash(this.name))(Hash.hash(this.path)));
|
|
95
|
+
}
|
|
96
|
+
toString() {
|
|
97
|
+
return `${this.name}@${this.version.current}`;
|
|
98
|
+
}
|
|
99
|
+
toJSON() {
|
|
100
|
+
return {
|
|
101
|
+
_tag: "AnalyzedWorkspace",
|
|
102
|
+
name: this.name,
|
|
103
|
+
version: this.version,
|
|
104
|
+
path: this.path,
|
|
105
|
+
root: this.root,
|
|
106
|
+
publishable: this.publishable,
|
|
107
|
+
targets: this.targets,
|
|
108
|
+
versioned: this.versioned,
|
|
109
|
+
tagged: this.tagged,
|
|
110
|
+
released: this.released
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
static publishable(workspaces) {
|
|
114
|
+
return workspaces.filter((w) => w.publishable);
|
|
115
|
+
}
|
|
116
|
+
static releasable(workspaces) {
|
|
117
|
+
return workspaces.filter((w) => w.released);
|
|
118
|
+
}
|
|
119
|
+
static findByName;
|
|
120
|
+
/** Pretty-print an AnalyzedWorkspace instance. */
|
|
121
|
+
static pretty;
|
|
122
|
+
};
|
|
123
|
+
AnalyzedWorkspace.findByName = Function.dual(2, (workspaces, name) => {
|
|
124
|
+
const found = workspaces.find((w) => w.name === name);
|
|
125
|
+
return found ? Option.some(found) : Option.none();
|
|
126
|
+
});
|
|
127
|
+
AnalyzedWorkspace.pretty = Pretty.make(AnalyzedWorkspace);
|
|
128
|
+
const PackageManagerInfo = Schema.Struct({
|
|
129
|
+
type: Schema.Literal("npm", "pnpm", "yarn", "bun"),
|
|
130
|
+
version: Schema.optional(Schema.String)
|
|
131
|
+
});
|
|
132
|
+
/**
|
|
133
|
+
* Full workspace analysis result containing all analyzed workspaces
|
|
134
|
+
* and project-level configuration.
|
|
135
|
+
*
|
|
136
|
+
* @since 0.2.0
|
|
137
|
+
*/
|
|
138
|
+
var WorkspaceAnalysis = class WorkspaceAnalysis extends Schema.TaggedClass()("WorkspaceAnalysis", {
|
|
139
|
+
root: Schema.String,
|
|
140
|
+
runtime: Schema.Literal("node", "bun"),
|
|
141
|
+
packageManager: PackageManagerInfo,
|
|
142
|
+
workspaces: Schema.Array(AnalyzedWorkspace),
|
|
143
|
+
changesetConfig: Schema.NullOr(Schema.Union(SilkChangesetConfigFile, ChangesetConfigFile)),
|
|
144
|
+
versioning: Schema.NullOr(VersioningStrategyResult),
|
|
145
|
+
tagStrategy: Schema.NullOr(TagStrategyType)
|
|
146
|
+
}) {
|
|
147
|
+
findWorkspace(name) {
|
|
148
|
+
const found = this.workspaces.find((w) => w.name === name);
|
|
149
|
+
return found ? Option.some(found) : Option.none();
|
|
150
|
+
}
|
|
151
|
+
get rootWorkspace() {
|
|
152
|
+
const root = this.workspaces.find((w) => w.root);
|
|
153
|
+
return root ? Option.some(root) : Option.none();
|
|
154
|
+
}
|
|
155
|
+
get publishableWorkspaces() {
|
|
156
|
+
return this.workspaces.filter((w) => w.publishable);
|
|
157
|
+
}
|
|
158
|
+
get versionedWorkspaces() {
|
|
159
|
+
return this.workspaces.filter((w) => w.versioned);
|
|
160
|
+
}
|
|
161
|
+
get taggedWorkspaces() {
|
|
162
|
+
return this.workspaces.filter((w) => w.tagged);
|
|
163
|
+
}
|
|
164
|
+
get releasableWorkspaces() {
|
|
165
|
+
return this.workspaces.filter((w) => w.released);
|
|
166
|
+
}
|
|
167
|
+
get isSilk() {
|
|
168
|
+
if (this.changesetConfig == null) return false;
|
|
169
|
+
return "_isSilk" in this.changesetConfig && this.changesetConfig._isSilk === true;
|
|
170
|
+
}
|
|
171
|
+
get hasChangesets() {
|
|
172
|
+
return this.changesetConfig != null;
|
|
173
|
+
}
|
|
174
|
+
[Equal.symbol](that) {
|
|
175
|
+
if (!(that instanceof WorkspaceAnalysis)) return false;
|
|
176
|
+
return this.root === that.root;
|
|
177
|
+
}
|
|
178
|
+
[Hash.symbol]() {
|
|
179
|
+
return Hash.cached(this)(Hash.hash(this.root));
|
|
180
|
+
}
|
|
181
|
+
toString() {
|
|
182
|
+
return `WorkspaceAnalysis(${this.root}, ${this.workspaces.length} workspaces)`;
|
|
183
|
+
}
|
|
184
|
+
/** Pretty-print a WorkspaceAnalysis instance. */
|
|
185
|
+
static pretty;
|
|
186
|
+
};
|
|
187
|
+
WorkspaceAnalysis.pretty = Pretty.make(WorkspaceAnalysis);
|
|
188
|
+
|
|
189
|
+
//#endregion
|
|
190
|
+
export { AnalyzedWorkspace, SilkPublishConfig, WorkspaceAnalysis };
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { BiomeSyncError } from "../errors/BiomeSyncError.js";
|
|
2
|
+
import { Context, Effect, Layer } from "effect";
|
|
3
|
+
import { FileSystem } from "@effect/platform";
|
|
4
|
+
import { parse } from "jsonc-effect";
|
|
5
|
+
|
|
6
|
+
//#region src/services/BiomeSchemaSync.ts
|
|
7
|
+
/**
|
|
8
|
+
* Strip leading semver range operators from a version string.
|
|
9
|
+
*
|
|
10
|
+
* @param version - Raw version string that may include `^`, `~`, `>=`, `<`, `=`, or `v` prefixes.
|
|
11
|
+
* @returns The bare semver string (e.g. `"1.9.3"`).
|
|
12
|
+
*
|
|
13
|
+
* @since 0.1.0
|
|
14
|
+
*/
|
|
15
|
+
function extractSemver(version) {
|
|
16
|
+
return version.replace(/^[\^~>=<v]+/, "");
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Build the expected Biome JSON schema URL for a given version.
|
|
20
|
+
*
|
|
21
|
+
* @param version - Bare semver string (e.g. `"1.9.3"`).
|
|
22
|
+
* @returns The canonical `biomejs.dev` schema URL for that version.
|
|
23
|
+
*
|
|
24
|
+
* @since 0.1.0
|
|
25
|
+
*/
|
|
26
|
+
function buildSchemaUrl(version) {
|
|
27
|
+
return `https://biomejs.dev/schemas/${version}/schema.json`;
|
|
28
|
+
}
|
|
29
|
+
const BIOME_SCHEMA_HOSTNAME = "biomejs.dev";
|
|
30
|
+
/** Check which biome config files exist in cwd. */
|
|
31
|
+
function findBiomeConfigs(cwd, fs) {
|
|
32
|
+
const candidates = [`${cwd}/biome.json`, `${cwd}/biome.jsonc`];
|
|
33
|
+
return Effect.gen(function* () {
|
|
34
|
+
const results = [];
|
|
35
|
+
for (const candidate of candidates) if (yield* fs.exists(candidate).pipe(Effect.orElseSucceed(() => false))) results.push(candidate);
|
|
36
|
+
return results;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Service that keeps the `$schema` URL in Biome config files in sync with a target version.
|
|
41
|
+
*
|
|
42
|
+
* @remarks
|
|
43
|
+
* Locates `biome.json` and `biome.jsonc` files in the working directory, then compares
|
|
44
|
+
* each file's `$schema` field against the expected `biomejs.dev` URL for the given version.
|
|
45
|
+
* `sync` writes updates in-place; `check` returns the same result without modifying files.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* const result = await Effect.runPromise(
|
|
50
|
+
* Effect.gen(function* () {
|
|
51
|
+
* const syncer = yield* BiomeSchemaSync;
|
|
52
|
+
* return yield* syncer.sync("^1.9.3");
|
|
53
|
+
* }).pipe(
|
|
54
|
+
* Effect.provide(BiomeSchemaSyncLive),
|
|
55
|
+
* Effect.provide(NodeContext.layer),
|
|
56
|
+
* )
|
|
57
|
+
* );
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* @since 0.1.0
|
|
61
|
+
*/
|
|
62
|
+
var BiomeSchemaSync = class extends Context.Tag("@savvy-web/silk-effects/BiomeSchemaSync")() {};
|
|
63
|
+
/**
|
|
64
|
+
* Live implementation of {@link BiomeSchemaSync}.
|
|
65
|
+
*
|
|
66
|
+
* @remarks
|
|
67
|
+
* Requires `FileSystem` from `@effect/platform`. Provide `NodeContext.layer` or
|
|
68
|
+
* `BunContext.layer` to satisfy this dependency.
|
|
69
|
+
*
|
|
70
|
+
* @since 0.1.0
|
|
71
|
+
*/
|
|
72
|
+
const BiomeSchemaSyncLive = Layer.effect(BiomeSchemaSync, Effect.gen(function* () {
|
|
73
|
+
const fs = yield* FileSystem.FileSystem;
|
|
74
|
+
const run = (version, options, write) => Effect.gen(function* () {
|
|
75
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
76
|
+
const expectedUrl = buildSchemaUrl(extractSemver(version));
|
|
77
|
+
const configs = yield* findBiomeConfigs(cwd, fs);
|
|
78
|
+
const updated = [];
|
|
79
|
+
const skipped = [];
|
|
80
|
+
const current = [];
|
|
81
|
+
for (const configPath of configs) {
|
|
82
|
+
const raw = yield* fs.readFileString(configPath).pipe(Effect.mapError(
|
|
83
|
+
/* v8 ignore next 4 -- error path requires a filesystem read failure */
|
|
84
|
+
(cause) => new BiomeSyncError({
|
|
85
|
+
path: configPath,
|
|
86
|
+
reason: String(cause)
|
|
87
|
+
})
|
|
88
|
+
));
|
|
89
|
+
const schema = (yield* parse(raw).pipe(Effect.mapError(
|
|
90
|
+
/* v8 ignore next 4 -- error path requires a JSONC parse failure */
|
|
91
|
+
(e) => new BiomeSyncError({
|
|
92
|
+
path: configPath,
|
|
93
|
+
reason: `Failed to parse JSONC: ${String(e)}`
|
|
94
|
+
})
|
|
95
|
+
))).$schema;
|
|
96
|
+
if (typeof schema !== "string") {
|
|
97
|
+
skipped.push(configPath);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (!schema.includes(BIOME_SCHEMA_HOSTNAME)) {
|
|
101
|
+
skipped.push(configPath);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (schema === expectedUrl) {
|
|
105
|
+
current.push(configPath);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (write) {
|
|
109
|
+
const updated_content = raw.replaceAll(schema, expectedUrl);
|
|
110
|
+
yield* fs.writeFileString(configPath, updated_content).pipe(Effect.mapError(
|
|
111
|
+
/* v8 ignore next 4 -- error path requires a filesystem write failure */
|
|
112
|
+
(cause) => new BiomeSyncError({
|
|
113
|
+
path: configPath,
|
|
114
|
+
reason: String(cause)
|
|
115
|
+
})
|
|
116
|
+
));
|
|
117
|
+
}
|
|
118
|
+
updated.push(configPath);
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
updated,
|
|
122
|
+
skipped,
|
|
123
|
+
current
|
|
124
|
+
};
|
|
125
|
+
});
|
|
126
|
+
return {
|
|
127
|
+
sync: (version, options) => run(version, options, true),
|
|
128
|
+
check: (version, options) => run(version, options, false)
|
|
129
|
+
};
|
|
130
|
+
}));
|
|
131
|
+
|
|
132
|
+
//#endregion
|
|
133
|
+
export { BiomeSchemaSync, BiomeSchemaSyncLive, buildSchemaUrl, extractSemver };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { ChangesetConfigReader } from "./ChangesetConfigReader.js";
|
|
2
|
+
import { Context, Effect, Layer, Option } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/services/ChangesetConfig.ts
|
|
5
|
+
/**
|
|
6
|
+
* Accessor service over a workspace root's `.changeset/config.json`.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* Reads through {@link ChangesetConfigReader} (FileSystem-based) with a per-root cache.
|
|
10
|
+
* Every accessor is total (error channel `never`): a missing or unreadable config collapses
|
|
11
|
+
* to `mode: "none"` and empty/false defaults.
|
|
12
|
+
*
|
|
13
|
+
* @since 0.4.0
|
|
14
|
+
*/
|
|
15
|
+
var ChangesetConfig = class extends Context.Tag("@savvy-web/silk-effects/ChangesetConfig")() {
|
|
16
|
+
/**
|
|
17
|
+
* The one ignore matcher: exact name match, or `@scope/*` wildcard.
|
|
18
|
+
*
|
|
19
|
+
* `"@scope/*"` matches `"@scope/anything"` (prefix kept includes the trailing slash),
|
|
20
|
+
* but not the bare scope `"@scope"`.
|
|
21
|
+
*/
|
|
22
|
+
static matches(name, pattern) {
|
|
23
|
+
if (pattern.endsWith("/*")) {
|
|
24
|
+
const prefix = pattern.slice(0, -1);
|
|
25
|
+
return name.startsWith(prefix);
|
|
26
|
+
}
|
|
27
|
+
return name === pattern;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const isSilk = (cfg) => "_isSilk" in cfg && cfg._isSilk === true;
|
|
31
|
+
/**
|
|
32
|
+
* Live {@link ChangesetConfig} reading via {@link ChangesetConfigReader}, cached per root.
|
|
33
|
+
*
|
|
34
|
+
* @remarks
|
|
35
|
+
* Requires `ChangesetConfigReader` (which requires `FileSystem`). Provide
|
|
36
|
+
* `ChangesetConfigReaderLive` + a platform layer (`NodeContext.layer`).
|
|
37
|
+
*
|
|
38
|
+
* @since 0.4.0
|
|
39
|
+
*/
|
|
40
|
+
const ChangesetConfigLive = Layer.effect(ChangesetConfig, Effect.gen(function* () {
|
|
41
|
+
const reader = yield* ChangesetConfigReader;
|
|
42
|
+
const cache = /* @__PURE__ */ new Map();
|
|
43
|
+
const read = (root) => Effect.gen(function* () {
|
|
44
|
+
const hit = cache.get(root);
|
|
45
|
+
if (hit !== void 0) return hit;
|
|
46
|
+
const result = yield* reader.read(root).pipe(Effect.option);
|
|
47
|
+
cache.set(root, result);
|
|
48
|
+
return result;
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
mode: (root) => read(root).pipe(Effect.map(Option.match({
|
|
52
|
+
onNone: () => "none",
|
|
53
|
+
onSome: (cfg) => isSilk(cfg) ? "silk" : "vanilla"
|
|
54
|
+
}))),
|
|
55
|
+
versionPrivate: (root) => read(root).pipe(Effect.map(Option.match({
|
|
56
|
+
onNone: () => false,
|
|
57
|
+
onSome: (cfg) => {
|
|
58
|
+
const pp = cfg.privatePackages;
|
|
59
|
+
return pp !== void 0 && pp !== false && pp.version === true;
|
|
60
|
+
}
|
|
61
|
+
}))),
|
|
62
|
+
ignorePatterns: (root) => read(root).pipe(Effect.map(Option.match({
|
|
63
|
+
onNone: () => [],
|
|
64
|
+
onSome: (cfg) => cfg.ignore ?? []
|
|
65
|
+
}))),
|
|
66
|
+
isIgnored: (name, root) => read(root).pipe(Effect.map(Option.match({
|
|
67
|
+
onNone: () => false,
|
|
68
|
+
onSome: (cfg) => (cfg.ignore ?? []).some((p) => ChangesetConfig.matches(name, p))
|
|
69
|
+
}))),
|
|
70
|
+
fixed: (root) => read(root).pipe(Effect.map(Option.match({
|
|
71
|
+
onNone: () => [],
|
|
72
|
+
onSome: (cfg) => cfg.fixed ?? []
|
|
73
|
+
})))
|
|
74
|
+
};
|
|
75
|
+
}));
|
|
76
|
+
|
|
77
|
+
//#endregion
|
|
78
|
+
export { ChangesetConfig, ChangesetConfigLive };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { ChangesetConfigError } from "../errors/ChangesetConfigError.js";
|
|
2
|
+
import { ChangesetConfigFile, SilkChangesetConfigFile } from "../schemas/VersioningSchemas.js";
|
|
3
|
+
import { Context, Effect, Layer, Schema } from "effect";
|
|
4
|
+
import { FileSystem } from "@effect/platform";
|
|
5
|
+
|
|
6
|
+
//#region src/services/ChangesetConfigReader.ts
|
|
7
|
+
/**
|
|
8
|
+
* Substrings that identify a Silk changelog adapter entry in `.changeset/config.json`.
|
|
9
|
+
*
|
|
10
|
+
* Both the standalone package (`@savvy-web/changesets/changelog`) and the
|
|
11
|
+
* consolidated Silk re-export (`@savvy-web/silk/changesets/changelog`) are Silk
|
|
12
|
+
* adapters. The `/silk/` segment in the consolidated path breaks the original
|
|
13
|
+
* `@savvy-web/changesets` substring, so each form needs its own marker.
|
|
14
|
+
*/
|
|
15
|
+
const SILK_CHANGELOG_MARKERS = ["@savvy-web/changesets", "@savvy-web/silk/changesets"];
|
|
16
|
+
function matchesSilkMarker(value) {
|
|
17
|
+
return SILK_CHANGELOG_MARKERS.some((marker) => value.includes(marker));
|
|
18
|
+
}
|
|
19
|
+
function isSilkChangelog(changelog) {
|
|
20
|
+
if (typeof changelog === "string") return matchesSilkMarker(changelog);
|
|
21
|
+
if (Array.isArray(changelog) && changelog.length > 0) return typeof changelog[0] === "string" && matchesSilkMarker(changelog[0]);
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Service that reads and decodes the `.changeset/config.json` for a given workspace root.
|
|
26
|
+
*
|
|
27
|
+
* @remarks
|
|
28
|
+
* Automatically detects whether the config uses the Silk changelog adapter
|
|
29
|
+
* (`@savvy-web/changesets`) and decodes as {@link SilkChangesetConfigFile} or the
|
|
30
|
+
* standard {@link ChangesetConfigFile} accordingly.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* const result = await Effect.runPromise(
|
|
35
|
+
* Effect.gen(function* () {
|
|
36
|
+
* const reader = yield* ChangesetConfigReader;
|
|
37
|
+
* return yield* reader.read(process.cwd());
|
|
38
|
+
* }).pipe(
|
|
39
|
+
* Effect.provide(ChangesetConfigReaderLive),
|
|
40
|
+
* Effect.provide(NodeContext.layer),
|
|
41
|
+
* )
|
|
42
|
+
* );
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @since 0.1.0
|
|
46
|
+
*/
|
|
47
|
+
var ChangesetConfigReader = class extends Context.Tag("@savvy-web/silk-effects/ChangesetConfigReader")() {};
|
|
48
|
+
/**
|
|
49
|
+
* Live implementation of {@link ChangesetConfigReader}.
|
|
50
|
+
*
|
|
51
|
+
* @remarks
|
|
52
|
+
* Requires `FileSystem` from `@effect/platform`. Provide `NodeContext.layer` or
|
|
53
|
+
* `BunContext.layer` to satisfy this dependency.
|
|
54
|
+
*
|
|
55
|
+
* @since 0.1.0
|
|
56
|
+
*/
|
|
57
|
+
const ChangesetConfigReaderLive = Layer.effect(ChangesetConfigReader, Effect.gen(function* () {
|
|
58
|
+
const fs = yield* FileSystem.FileSystem;
|
|
59
|
+
const read = (root) => {
|
|
60
|
+
const configPath = `${root}/.changeset/config.json`;
|
|
61
|
+
return Effect.gen(function* () {
|
|
62
|
+
if (!(yield* fs.exists(configPath).pipe(Effect.mapError(
|
|
63
|
+
/* v8 ignore next 4 -- error path requires fs.exists to fail */
|
|
64
|
+
(cause) => new ChangesetConfigError({
|
|
65
|
+
path: configPath,
|
|
66
|
+
reason: String(cause)
|
|
67
|
+
})
|
|
68
|
+
)))) return yield* Effect.fail(new ChangesetConfigError({
|
|
69
|
+
path: configPath,
|
|
70
|
+
reason: "File not found"
|
|
71
|
+
}));
|
|
72
|
+
const raw = yield* fs.readFileString(configPath).pipe(Effect.mapError(
|
|
73
|
+
/* v8 ignore next 4 -- error path requires fs.readFileString to fail */
|
|
74
|
+
(cause) => new ChangesetConfigError({
|
|
75
|
+
path: configPath,
|
|
76
|
+
reason: String(cause)
|
|
77
|
+
})
|
|
78
|
+
));
|
|
79
|
+
const parsed = yield* Effect.try({
|
|
80
|
+
try: () => JSON.parse(raw),
|
|
81
|
+
catch: (cause) => new ChangesetConfigError({
|
|
82
|
+
path: configPath,
|
|
83
|
+
reason: `Invalid JSON: ${String(cause)}`
|
|
84
|
+
})
|
|
85
|
+
});
|
|
86
|
+
if (isSilkChangelog(parsed.changelog)) return yield* Schema.decodeUnknown(SilkChangesetConfigFile)(parsed).pipe(Effect.mapError(
|
|
87
|
+
/* v8 ignore next 4 -- error path requires schema decode failure */
|
|
88
|
+
(cause) => new ChangesetConfigError({
|
|
89
|
+
path: configPath,
|
|
90
|
+
reason: `Schema decode failed: ${String(cause)}`
|
|
91
|
+
})
|
|
92
|
+
));
|
|
93
|
+
return yield* Schema.decodeUnknown(ChangesetConfigFile)(parsed).pipe(Effect.mapError(
|
|
94
|
+
/* v8 ignore next 4 -- error path requires schema decode failure */
|
|
95
|
+
(cause) => new ChangesetConfigError({
|
|
96
|
+
path: configPath,
|
|
97
|
+
reason: `Schema decode failed: ${String(cause)}`
|
|
98
|
+
})
|
|
99
|
+
));
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
return { read };
|
|
103
|
+
}));
|
|
104
|
+
|
|
105
|
+
//#endregion
|
|
106
|
+
export { ChangesetConfigReader, ChangesetConfigReaderLive };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Context, Effect, Layer } from "effect";
|
|
2
|
+
import { FileSystem } from "@effect/platform";
|
|
3
|
+
|
|
4
|
+
//#region src/services/ConfigDiscovery.ts
|
|
5
|
+
/**
|
|
6
|
+
* Service that locates named config files within a workspace using priority-ordered search paths.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* Search priority (highest to lowest):
|
|
10
|
+
* 1. `lib/configs/{name}` — shared config provided by a dependency package.
|
|
11
|
+
* 2. `{cwd}/{name}` — local override at the workspace root.
|
|
12
|
+
*
|
|
13
|
+
* Missing files are silently skipped; only files that actually exist are returned.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const result = await Effect.runPromise(
|
|
18
|
+
* Effect.gen(function* () {
|
|
19
|
+
* const discovery = yield* ConfigDiscovery;
|
|
20
|
+
* return yield* discovery.find("biome.json");
|
|
21
|
+
* }).pipe(
|
|
22
|
+
* Effect.provide(ConfigDiscoveryLive),
|
|
23
|
+
* Effect.provide(NodeContext.layer),
|
|
24
|
+
* )
|
|
25
|
+
* );
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @since 0.1.0
|
|
29
|
+
*/
|
|
30
|
+
var ConfigDiscovery = class extends Context.Tag("@savvy-web/silk-effects/ConfigDiscovery")() {};
|
|
31
|
+
/**
|
|
32
|
+
* Check whether a path exists, treating any PlatformError as "not found".
|
|
33
|
+
*/
|
|
34
|
+
function safeExists(fs, path) {
|
|
35
|
+
return fs.exists(path).pipe(Effect.orElseSucceed(() => false));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Live implementation of {@link ConfigDiscovery}.
|
|
39
|
+
*
|
|
40
|
+
* @remarks
|
|
41
|
+
* Requires `FileSystem` from `@effect/platform`. Provide `NodeContext.layer` or
|
|
42
|
+
* `BunContext.layer` to satisfy this dependency.
|
|
43
|
+
*
|
|
44
|
+
* @since 0.1.0
|
|
45
|
+
*/
|
|
46
|
+
const ConfigDiscoveryLive = Layer.effect(ConfigDiscovery, Effect.gen(function* () {
|
|
47
|
+
const fs = yield* FileSystem.FileSystem;
|
|
48
|
+
const findAll = (name, options) => Effect.gen(function* () {
|
|
49
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
50
|
+
const results = [];
|
|
51
|
+
const libPath = `${cwd}/lib/configs/${name}`;
|
|
52
|
+
if (yield* safeExists(fs, libPath)) results.push({
|
|
53
|
+
path: libPath,
|
|
54
|
+
source: "lib"
|
|
55
|
+
});
|
|
56
|
+
const rootPath = `${cwd}/${name}`;
|
|
57
|
+
if (yield* safeExists(fs, rootPath)) results.push({
|
|
58
|
+
path: rootPath,
|
|
59
|
+
source: "root"
|
|
60
|
+
});
|
|
61
|
+
return results;
|
|
62
|
+
});
|
|
63
|
+
const find = (name, options) => findAll(name, options).pipe(Effect.map((results) => results[0] ?? null));
|
|
64
|
+
return {
|
|
65
|
+
find,
|
|
66
|
+
findAll
|
|
67
|
+
};
|
|
68
|
+
}));
|
|
69
|
+
|
|
70
|
+
//#endregion
|
|
71
|
+
export { ConfigDiscovery, ConfigDiscoveryLive };
|