@savvy-web/silk-effects 0.6.1 → 1.0.1
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,288 @@
|
|
|
1
|
+
import { SectionParseError } from "../errors/SectionParseError.js";
|
|
2
|
+
import { SectionWriteError } from "../errors/SectionWriteError.js";
|
|
3
|
+
import { CheckResult, SyncResult } from "../schemas/SectionResults.js";
|
|
4
|
+
import { SectionBlock } from "../schemas/SectionBlock.js";
|
|
5
|
+
import { Context, Effect, Equal, Function, Layer } from "effect";
|
|
6
|
+
import { FileSystem } from "@effect/platform";
|
|
7
|
+
|
|
8
|
+
//#region src/services/ManagedSection.ts
|
|
9
|
+
function beginMarker(toolName, commentStyle) {
|
|
10
|
+
return `${commentStyle} --- BEGIN ${toolName.toUpperCase()} MANAGED SECTION ---`;
|
|
11
|
+
}
|
|
12
|
+
function endMarker(toolName, commentStyle) {
|
|
13
|
+
return `${commentStyle} --- END ${toolName.toUpperCase()} MANAGED SECTION ---`;
|
|
14
|
+
}
|
|
15
|
+
function parseContent(content, toolName, commentStyle) {
|
|
16
|
+
const begin = beginMarker(toolName, commentStyle);
|
|
17
|
+
const end = endMarker(toolName, commentStyle);
|
|
18
|
+
const beginIndex = content.indexOf(begin);
|
|
19
|
+
const endIndex = content.indexOf(end);
|
|
20
|
+
if (beginIndex === -1 || endIndex === -1 || endIndex <= beginIndex) return null;
|
|
21
|
+
let managed = content.slice(beginIndex + begin.length, endIndex);
|
|
22
|
+
if (managed.startsWith("\n")) managed = managed.slice(1);
|
|
23
|
+
if (managed.endsWith("\n")) managed = managed.slice(0, -1);
|
|
24
|
+
return {
|
|
25
|
+
before: content.slice(0, beginIndex),
|
|
26
|
+
managed,
|
|
27
|
+
after: content.slice(endIndex + end.length)
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function assembleContent(before, managed, after, toolName, commentStyle) {
|
|
31
|
+
return `${before}${beginMarker(toolName, commentStyle)}\n${managed}\n${endMarker(toolName, commentStyle)}${after}`;
|
|
32
|
+
}
|
|
33
|
+
const BEGIN_MARKER_RE = /^(#|\/\/) --- BEGIN (.+?) MANAGED SECTION ---$/gm;
|
|
34
|
+
function sectionKey(toolName, commentStyle) {
|
|
35
|
+
return `${toolName.toUpperCase()}::${commentStyle}`;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Locate every managed section in `content`, in document order, across all tools and
|
|
39
|
+
* comment styles. Unterminated begin markers are skipped.
|
|
40
|
+
*/
|
|
41
|
+
function findAllSections(content) {
|
|
42
|
+
const results = [];
|
|
43
|
+
for (const match of content.matchAll(BEGIN_MARKER_RE)) {
|
|
44
|
+
const style = match[1];
|
|
45
|
+
const name = match[2];
|
|
46
|
+
const beginStart = match.index;
|
|
47
|
+
const beginEnd = beginStart + match[0].length;
|
|
48
|
+
const end = `${style} --- END ${name} MANAGED SECTION ---`;
|
|
49
|
+
const endIdx = content.indexOf(end, beginEnd);
|
|
50
|
+
if (endIdx === -1) continue;
|
|
51
|
+
let inner = content.slice(beginEnd, endIdx);
|
|
52
|
+
if (inner.startsWith("\n")) inner = inner.slice(1);
|
|
53
|
+
if (inner.endsWith("\n")) inner = inner.slice(0, -1);
|
|
54
|
+
results.push({
|
|
55
|
+
key: sectionKey(name, style),
|
|
56
|
+
commentStyle: style,
|
|
57
|
+
content: inner,
|
|
58
|
+
raw: content.slice(beginStart, endIdx + end.length),
|
|
59
|
+
start: beginStart,
|
|
60
|
+
end: endIdx + end.length
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return results;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Service for managing delimited sections in user-editable files.
|
|
67
|
+
*
|
|
68
|
+
* All methods use dual API (data-first and data-last).
|
|
69
|
+
* Identity-only operations (`read`, `isManaged`) take a {@link SectionDefinition}.
|
|
70
|
+
* Content operations (`write`, `sync`, `check`) take a {@link SectionBlock}.
|
|
71
|
+
*
|
|
72
|
+
* @since 0.2.0
|
|
73
|
+
*/
|
|
74
|
+
var ManagedSection = class extends Context.Tag("@savvy-web/silk-effects/ManagedSection")() {};
|
|
75
|
+
/**
|
|
76
|
+
* Live implementation of {@link ManagedSection} backed by `@effect/platform` FileSystem.
|
|
77
|
+
*
|
|
78
|
+
* @since 0.2.0
|
|
79
|
+
*/
|
|
80
|
+
const ManagedSectionLive = Layer.effect(ManagedSection, Effect.gen(function* () {
|
|
81
|
+
const fs = yield* FileSystem.FileSystem;
|
|
82
|
+
const read = Function.dual(2, (path, definition) => Effect.gen(function* () {
|
|
83
|
+
if (!(yield* fs.exists(path).pipe(Effect.orElseSucceed(() => false)))) return null;
|
|
84
|
+
const parsed = parseContent(yield* fs.readFileString(path).pipe(Effect.mapError((cause) => new SectionParseError({
|
|
85
|
+
path,
|
|
86
|
+
reason: String(cause)
|
|
87
|
+
}))), definition.toolName, definition.commentStyle);
|
|
88
|
+
if (parsed === null) return null;
|
|
89
|
+
return SectionBlock.make({
|
|
90
|
+
toolName: definition.toolName,
|
|
91
|
+
commentStyle: definition.commentStyle,
|
|
92
|
+
content: parsed.managed
|
|
93
|
+
});
|
|
94
|
+
}));
|
|
95
|
+
const isManaged = Function.dual(2, (path, definition) => Effect.gen(function* () {
|
|
96
|
+
if (!(yield* fs.exists(path).pipe(Effect.orElseSucceed(() => false)))) return false;
|
|
97
|
+
const raw = yield* fs.readFileString(path).pipe(Effect.orElseSucceed(() => ""));
|
|
98
|
+
const begin = beginMarker(definition.toolName, definition.commentStyle);
|
|
99
|
+
const end = endMarker(definition.toolName, definition.commentStyle);
|
|
100
|
+
const beginIdx = raw.indexOf(begin);
|
|
101
|
+
const endIdx = raw.indexOf(end);
|
|
102
|
+
return beginIdx !== -1 && endIdx !== -1 && endIdx > beginIdx;
|
|
103
|
+
}));
|
|
104
|
+
const write = Function.dual(2, (path, block) => Effect.gen(function* () {
|
|
105
|
+
const exists = yield* fs.exists(path).pipe(Effect.orElseSucceed(() => false));
|
|
106
|
+
let fileContent;
|
|
107
|
+
if (exists) {
|
|
108
|
+
const raw = yield* fs.readFileString(path).pipe(Effect.mapError((cause) => new SectionWriteError({
|
|
109
|
+
path,
|
|
110
|
+
reason: String(cause)
|
|
111
|
+
})));
|
|
112
|
+
const parsed = parseContent(raw, block.toolName, block.commentStyle);
|
|
113
|
+
if (parsed !== null) fileContent = assembleContent(parsed.before, block.content, parsed.after, block.toolName, block.commentStyle);
|
|
114
|
+
else {
|
|
115
|
+
const trimmed = raw.trimEnd();
|
|
116
|
+
const begin = beginMarker(block.toolName, block.commentStyle);
|
|
117
|
+
const end = endMarker(block.toolName, block.commentStyle);
|
|
118
|
+
fileContent = `${trimmed}\n\n${begin}\n${block.content}\n${end}\n`;
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
const begin = beginMarker(block.toolName, block.commentStyle);
|
|
122
|
+
const end = endMarker(block.toolName, block.commentStyle);
|
|
123
|
+
fileContent = `${begin}\n${block.content}\n${end}\n`;
|
|
124
|
+
}
|
|
125
|
+
yield* fs.writeFileString(path, fileContent).pipe(Effect.mapError((cause) => new SectionWriteError({
|
|
126
|
+
path,
|
|
127
|
+
reason: String(cause)
|
|
128
|
+
})));
|
|
129
|
+
}));
|
|
130
|
+
return {
|
|
131
|
+
read,
|
|
132
|
+
write,
|
|
133
|
+
isManaged,
|
|
134
|
+
sync: Function.dual(2, (path, block) => Effect.gen(function* () {
|
|
135
|
+
const onDisk = yield* read(path, {
|
|
136
|
+
toolName: block.toolName,
|
|
137
|
+
commentStyle: block.commentStyle
|
|
138
|
+
}).pipe(Effect.mapError((cause) => new SectionWriteError({
|
|
139
|
+
path,
|
|
140
|
+
reason: String(cause)
|
|
141
|
+
})));
|
|
142
|
+
if (onDisk === null) {
|
|
143
|
+
yield* write(path, block);
|
|
144
|
+
return SyncResult.Created();
|
|
145
|
+
}
|
|
146
|
+
if (Equal.equals(onDisk, block)) return SyncResult.Unchanged();
|
|
147
|
+
const d = SectionBlock.diff(onDisk, block);
|
|
148
|
+
yield* write(path, block);
|
|
149
|
+
return SyncResult.Updated({ diff: d });
|
|
150
|
+
})),
|
|
151
|
+
syncMany: Function.dual(2, (path, blocks) => Effect.gen(function* () {
|
|
152
|
+
const original = (yield* fs.exists(path).pipe(Effect.orElseSucceed(() => false))) ? yield* fs.readFileString(path).pipe(Effect.mapError((cause) => new SectionWriteError({
|
|
153
|
+
path,
|
|
154
|
+
reason: String(cause)
|
|
155
|
+
}))) : "";
|
|
156
|
+
const keyOf = (b) => sectionKey(b.toolName, b.commentStyle);
|
|
157
|
+
const found = findAllSections(original);
|
|
158
|
+
const onDiskByKey = /* @__PURE__ */ new Map();
|
|
159
|
+
for (const f of found) if (!onDiskByKey.has(f.key)) onDiskByKey.set(f.key, f);
|
|
160
|
+
const results = blocks.map((block) => {
|
|
161
|
+
const onDisk = onDiskByKey.get(keyOf(block));
|
|
162
|
+
if (onDisk === void 0) return SyncResult.Created();
|
|
163
|
+
const current = SectionBlock.make({
|
|
164
|
+
toolName: block.toolName,
|
|
165
|
+
commentStyle: block.commentStyle,
|
|
166
|
+
content: onDisk.content
|
|
167
|
+
});
|
|
168
|
+
if (Equal.equals(current, block)) return SyncResult.Unchanged();
|
|
169
|
+
return SyncResult.Updated({ diff: SectionBlock.diff(current, block) });
|
|
170
|
+
});
|
|
171
|
+
const items = [];
|
|
172
|
+
let cursor = 0;
|
|
173
|
+
for (const f of found) {
|
|
174
|
+
items.push({
|
|
175
|
+
kind: "text",
|
|
176
|
+
value: original.slice(cursor, f.start)
|
|
177
|
+
});
|
|
178
|
+
items.push({
|
|
179
|
+
kind: "section",
|
|
180
|
+
key: f.key,
|
|
181
|
+
raw: f.raw,
|
|
182
|
+
render: null
|
|
183
|
+
});
|
|
184
|
+
cursor = f.end;
|
|
185
|
+
}
|
|
186
|
+
items.push({
|
|
187
|
+
kind: "text",
|
|
188
|
+
value: original.slice(cursor)
|
|
189
|
+
});
|
|
190
|
+
const targetKeys = new Set(blocks.map(keyOf));
|
|
191
|
+
const slotIndices = [];
|
|
192
|
+
items.forEach((item, idx) => {
|
|
193
|
+
if (item.kind === "section" && targetKeys.has(item.key)) slotIndices.push(idx);
|
|
194
|
+
});
|
|
195
|
+
const itemIndexByDeclared = /* @__PURE__ */ new Map();
|
|
196
|
+
let slotCursor = 0;
|
|
197
|
+
blocks.forEach((block, declaredIdx) => {
|
|
198
|
+
if (!onDiskByKey.has(keyOf(block))) return;
|
|
199
|
+
if (slotCursor >= slotIndices.length) return;
|
|
200
|
+
const itemIdx = slotIndices[slotCursor];
|
|
201
|
+
const item = items[itemIdx];
|
|
202
|
+
if (item.kind === "section") item.render = block;
|
|
203
|
+
itemIndexByDeclared.set(declaredIdx, itemIdx);
|
|
204
|
+
slotCursor += 1;
|
|
205
|
+
});
|
|
206
|
+
const beforeAnchor = /* @__PURE__ */ new Map();
|
|
207
|
+
const afterAnchor = /* @__PURE__ */ new Map();
|
|
208
|
+
const appendList = [];
|
|
209
|
+
const pushInto = (map, anchor, block) => {
|
|
210
|
+
const arr = map.get(anchor) ?? [];
|
|
211
|
+
arr.push(block);
|
|
212
|
+
map.set(anchor, arr);
|
|
213
|
+
};
|
|
214
|
+
blocks.forEach((block, i) => {
|
|
215
|
+
if (onDiskByKey.has(keyOf(block))) return;
|
|
216
|
+
let placed = false;
|
|
217
|
+
for (let j = i + 1; j < blocks.length && !placed; j += 1) {
|
|
218
|
+
const itemIdx = itemIndexByDeclared.get(j);
|
|
219
|
+
if (itemIdx !== void 0) {
|
|
220
|
+
pushInto(beforeAnchor, itemIdx, block);
|
|
221
|
+
placed = true;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
for (let j = i - 1; j >= 0 && !placed; j -= 1) {
|
|
225
|
+
const itemIdx = itemIndexByDeclared.get(j);
|
|
226
|
+
if (itemIdx !== void 0) {
|
|
227
|
+
pushInto(afterAnchor, itemIdx, block);
|
|
228
|
+
placed = true;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (!placed) appendList.push(block);
|
|
232
|
+
});
|
|
233
|
+
const out = [];
|
|
234
|
+
items.forEach((item, idx) => {
|
|
235
|
+
if (item.kind === "text") {
|
|
236
|
+
out.push(item.value);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
for (const block of beforeAnchor.get(idx) ?? []) out.push(block.rendered, "\n\n");
|
|
240
|
+
out.push(item.render === null ? item.raw : item.render.rendered);
|
|
241
|
+
for (const block of afterAnchor.get(idx) ?? []) out.push("\n\n", block.rendered);
|
|
242
|
+
});
|
|
243
|
+
let output = out.join("");
|
|
244
|
+
for (const block of appendList) output = output.trim() === "" ? `${block.rendered}\n` : `${output.replace(/\n+$/, "")}\n\n${block.rendered}\n`;
|
|
245
|
+
if (output !== "" && !output.endsWith("\n")) output += "\n";
|
|
246
|
+
if (output !== original) yield* fs.writeFileString(path, output).pipe(Effect.mapError((cause) => new SectionWriteError({
|
|
247
|
+
path,
|
|
248
|
+
reason: String(cause)
|
|
249
|
+
})));
|
|
250
|
+
return results;
|
|
251
|
+
})),
|
|
252
|
+
check: Function.dual(2, (path, block) => Effect.gen(function* () {
|
|
253
|
+
const onDisk = yield* read(path, {
|
|
254
|
+
toolName: block.toolName,
|
|
255
|
+
commentStyle: block.commentStyle
|
|
256
|
+
});
|
|
257
|
+
if (onDisk === null) return CheckResult.NotFound();
|
|
258
|
+
const isUpToDate = Equal.equals(onDisk, block);
|
|
259
|
+
const d = SectionBlock.diff(onDisk, block);
|
|
260
|
+
return CheckResult.Found({
|
|
261
|
+
isUpToDate,
|
|
262
|
+
diff: d
|
|
263
|
+
});
|
|
264
|
+
})),
|
|
265
|
+
remove: Function.dual(2, (path, definition) => Effect.gen(function* () {
|
|
266
|
+
if (!(yield* fs.exists(path).pipe(Effect.orElseSucceed(() => false)))) return false;
|
|
267
|
+
const parsed = parseContent(yield* fs.readFileString(path).pipe(Effect.mapError((cause) => new SectionWriteError({
|
|
268
|
+
path,
|
|
269
|
+
reason: String(cause)
|
|
270
|
+
}))), definition.toolName, definition.commentStyle);
|
|
271
|
+
if (parsed === null) return false;
|
|
272
|
+
const before = parsed.before.replace(/\n+$/, "");
|
|
273
|
+
const after = parsed.after.replace(/^\n+/, "");
|
|
274
|
+
let next;
|
|
275
|
+
if (before !== "" && after !== "") next = `${before}\n\n${after}`;
|
|
276
|
+
else if (before !== "") next = `${before}\n`;
|
|
277
|
+
else next = after;
|
|
278
|
+
yield* fs.writeFileString(path, next).pipe(Effect.mapError((cause) => new SectionWriteError({
|
|
279
|
+
path,
|
|
280
|
+
reason: String(cause)
|
|
281
|
+
})));
|
|
282
|
+
return true;
|
|
283
|
+
}))
|
|
284
|
+
};
|
|
285
|
+
}));
|
|
286
|
+
|
|
287
|
+
//#endregion
|
|
288
|
+
export { ManagedSection, ManagedSectionLive };
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { ChangesetConfig } from "./ChangesetConfig.js";
|
|
2
|
+
import { Effect, Layer } from "effect";
|
|
3
|
+
import { isAbsolute, join } from "node:path";
|
|
4
|
+
import { PublishTarget, PublishabilityDetector, PublishabilityDetectorLive, WorkspaceDiscovery } from "workspaces-effect";
|
|
5
|
+
import { FileSystem } from "@effect/platform";
|
|
6
|
+
|
|
7
|
+
//#region src/services/SilkPublishability.ts
|
|
8
|
+
const NPM_DEFAULT = "https://registry.npmjs.org/";
|
|
9
|
+
/**
|
|
10
|
+
* Default registry endpoints for the well-known target keys. Kept in sync with the
|
|
11
|
+
* bundler's `resolveTargets` so the pre-build fallback matches what the binding will carry
|
|
12
|
+
* (no trailing slash).
|
|
13
|
+
*/
|
|
14
|
+
const DEFAULT_REGISTRIES = {
|
|
15
|
+
npm: "https://registry.npmjs.org",
|
|
16
|
+
github: "https://npm.pkg.github.com"
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Silk publishability rules over `workspaces-effect`'s {@link PublishTarget}.
|
|
20
|
+
*
|
|
21
|
+
* @remarks
|
|
22
|
+
* In silk mode `private: true` is the norm on workspace `package.json`; publishability is
|
|
23
|
+
* derived from `publishConfig`, with the `private` flag consulted only as a last-resort
|
|
24
|
+
* default. All helpers are static so a consumer sees the full rule surface in one place.
|
|
25
|
+
*
|
|
26
|
+
* @since 0.4.0
|
|
27
|
+
*/
|
|
28
|
+
var SilkPublishability = class {
|
|
29
|
+
/**
|
|
30
|
+
* Apply silk publishability rules to a raw `package.json` and the bundler's resolved
|
|
31
|
+
* target binding. Targets-first precedence:
|
|
32
|
+
*
|
|
33
|
+
* - A non-empty `publishConfig.targets` map (the bundler's Record-map form) makes the
|
|
34
|
+
* package publishable regardless of `private`. With a `binding` (post-prod-build), one
|
|
35
|
+
* {@link PublishTarget} is emitted per resolved registry target, its `directory` set to
|
|
36
|
+
* the bound group's `dist/prod/<group>/pkg` dir. Without a binding (pre-build), one
|
|
37
|
+
* placeholder target is emitted per declared key so publishability and target counts
|
|
38
|
+
* are correct; the directory is best-effort and unused until the build writes the
|
|
39
|
+
* binding.
|
|
40
|
+
* - Else `publishConfig.access` → one target at `publishConfig.directory`.
|
|
41
|
+
* - Else `private !== true` → one default public target.
|
|
42
|
+
* - Else `[]`.
|
|
43
|
+
*
|
|
44
|
+
* @param pkgName - The package's name (the base name for `true`/empty-object targets).
|
|
45
|
+
* @param raw - The raw `package.json` (silk reads the unschematized `publishConfig`).
|
|
46
|
+
* @param binding - The parsed `dist/prod/targets.json` binding, or `null` when the prod
|
|
47
|
+
* build has not run yet. See {@link readTargetsBinding}.
|
|
48
|
+
*/
|
|
49
|
+
static detect(pkgName, raw, binding) {
|
|
50
|
+
const pc = raw.publishConfig;
|
|
51
|
+
const targets = pc?.targets;
|
|
52
|
+
if (targets && typeof targets === "object" && !Array.isArray(targets) && Object.keys(targets).length > 0) {
|
|
53
|
+
const access = pc?.access ?? "public";
|
|
54
|
+
if (binding) {
|
|
55
|
+
const dirByGroup = new Map(binding.groups.map((g) => [g.id, g.dir]));
|
|
56
|
+
return binding.targets.map((t) => new PublishTarget({
|
|
57
|
+
name: t.name,
|
|
58
|
+
registry: t.registry,
|
|
59
|
+
directory: dirByGroup.get(t.group) ?? `dist/prod/${t.group}/pkg`,
|
|
60
|
+
access
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
return Object.keys(targets).map((id) => new PublishTarget({
|
|
64
|
+
name: pkgName,
|
|
65
|
+
registry: DEFAULT_REGISTRIES[id] ?? pc?.registry ?? NPM_DEFAULT,
|
|
66
|
+
directory: `dist/prod/${id}/pkg`,
|
|
67
|
+
access
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
if (pc && (pc.access === "public" || pc.access === "restricted")) return [new PublishTarget({
|
|
71
|
+
name: pkgName,
|
|
72
|
+
registry: pc.registry ?? NPM_DEFAULT,
|
|
73
|
+
directory: pc.directory ?? ".",
|
|
74
|
+
access: pc.access
|
|
75
|
+
})];
|
|
76
|
+
if (raw.private !== true) return [new PublishTarget({
|
|
77
|
+
name: pkgName,
|
|
78
|
+
registry: pc?.registry ?? NPM_DEFAULT,
|
|
79
|
+
directory: pc?.directory ?? ".",
|
|
80
|
+
access: pc?.access ?? "public"
|
|
81
|
+
})];
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Resolve a package's publish targets via {@link PublishabilityDetector}, then drop any
|
|
86
|
+
* whose built `directory` package.json is `private: true`. Returned targets keep the
|
|
87
|
+
* detector's original (possibly package-relative) `directory`.
|
|
88
|
+
*/
|
|
89
|
+
static resolveTargets(pkg, root) {
|
|
90
|
+
return Effect.gen(function* () {
|
|
91
|
+
const detector = yield* PublishabilityDetector;
|
|
92
|
+
const fs = yield* FileSystem.FileSystem;
|
|
93
|
+
const targets = yield* detector.detect(pkg, root);
|
|
94
|
+
const kept = [];
|
|
95
|
+
for (const t of targets) if (!(yield* isTargetPrivate(fs, isAbsolute(t.directory) ? t.directory : join(pkg.path, t.directory)))) kept.push(t);
|
|
96
|
+
return kept;
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The publishable, non-ignored packages, resolved through the single
|
|
101
|
+
* {@link PublishabilityDetector} (which already honors changeset ignore in adaptive mode).
|
|
102
|
+
*/
|
|
103
|
+
static listPublishable(root) {
|
|
104
|
+
return Effect.gen(function* () {
|
|
105
|
+
const discovery = yield* WorkspaceDiscovery;
|
|
106
|
+
const detector = yield* PublishabilityDetector;
|
|
107
|
+
const packages = yield* discovery.listPackages().pipe(Effect.orDie);
|
|
108
|
+
const out = [];
|
|
109
|
+
for (const pkg of packages) {
|
|
110
|
+
const targets = yield* detector.detect(pkg, root);
|
|
111
|
+
if (targets.length > 0) out.push({
|
|
112
|
+
name: pkg.name,
|
|
113
|
+
version: pkg.version,
|
|
114
|
+
path: pkg.path,
|
|
115
|
+
targetCount: targets.length
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
/** True when a built target directory's package.json is `private: true`. Missing/unreadable/malformed → false. */
|
|
123
|
+
const isTargetPrivate = (fs, targetDir) => fs.readFileString(join(targetDir, "package.json")).pipe(Effect.flatMap((content) => Effect.try({
|
|
124
|
+
try: () => JSON.parse(content).private === true,
|
|
125
|
+
catch: () => /* @__PURE__ */ new Error("invalid package.json")
|
|
126
|
+
})), Effect.orElseSucceed(() => false));
|
|
127
|
+
/** Read a raw `package.json` from disk via FileSystem; missing/unreadable/malformed → null. */
|
|
128
|
+
const readRaw = (fs, packageJsonPath) => fs.readFileString(packageJsonPath).pipe(Effect.flatMap((content) => Effect.try({
|
|
129
|
+
try: () => JSON.parse(content),
|
|
130
|
+
catch: () => /* @__PURE__ */ new Error("invalid package.json")
|
|
131
|
+
})), Effect.orElseSucceed(() => null));
|
|
132
|
+
/**
|
|
133
|
+
* Read the bundler's `<pkgPath>/dist/prod/targets.json` binding via FileSystem.
|
|
134
|
+
*
|
|
135
|
+
* @remarks
|
|
136
|
+
* Returns `null` when the file is missing/unreadable/malformed — i.e. before the prod build
|
|
137
|
+
* has run. {@link SilkPublishability.detect} falls back to declared-key placeholders in that
|
|
138
|
+
* case. Used by the silk {@link PublishabilityDetector} layers and the workspace analyzer.
|
|
139
|
+
*
|
|
140
|
+
* @param fs - The FileSystem service.
|
|
141
|
+
* @param pkgPath - Absolute path to the package directory.
|
|
142
|
+
* @since 1.0.0
|
|
143
|
+
*/
|
|
144
|
+
const readTargetsBinding = (fs, pkgPath) => fs.readFileString(join(pkgPath, "dist", "prod", "targets.json")).pipe(Effect.flatMap((content) => Effect.try({
|
|
145
|
+
try: () => JSON.parse(content),
|
|
146
|
+
catch: () => /* @__PURE__ */ new Error("invalid targets.json")
|
|
147
|
+
})), Effect.orElseSucceed(() => null));
|
|
148
|
+
/**
|
|
149
|
+
* Override of `workspaces-effect`'s {@link PublishabilityDetector} Tag with pure silk rules.
|
|
150
|
+
*
|
|
151
|
+
* @remarks Requires `FileSystem` (captured at layer build); `detect` reads the raw
|
|
152
|
+
* `package.json` from `pkg.packageJsonPath` and applies {@link SilkPublishability.detect}.
|
|
153
|
+
*
|
|
154
|
+
* @since 0.4.0
|
|
155
|
+
*/
|
|
156
|
+
const SilkPublishabilityDetectorLive = Layer.effect(PublishabilityDetector, Effect.gen(function* () {
|
|
157
|
+
const fs = yield* FileSystem.FileSystem;
|
|
158
|
+
return { detect: (pkg, _root) => Effect.gen(function* () {
|
|
159
|
+
const raw = yield* readRaw(fs, pkg.packageJsonPath);
|
|
160
|
+
if (!raw) return [];
|
|
161
|
+
const binding = yield* readTargetsBinding(fs, pkg.path);
|
|
162
|
+
return SilkPublishability.detect(pkg.name, raw, binding);
|
|
163
|
+
}) };
|
|
164
|
+
}));
|
|
165
|
+
/**
|
|
166
|
+
* Ignore-aware override of {@link PublishabilityDetector}. `detect` short-circuits to `[]`
|
|
167
|
+
* for changeset-ignored packages, then dispatches on {@link ChangesetConfig.mode}:
|
|
168
|
+
* `none` → `[]`; `silk` → {@link SilkPublishability.detect}; `vanilla` → the library default.
|
|
169
|
+
*
|
|
170
|
+
* @remarks Requires `FileSystem` + {@link ChangesetConfig} at build.
|
|
171
|
+
*
|
|
172
|
+
* @since 0.4.0
|
|
173
|
+
*/
|
|
174
|
+
const PublishabilityDetectorAdaptiveLive = Layer.effect(PublishabilityDetector, Effect.gen(function* () {
|
|
175
|
+
const fs = yield* FileSystem.FileSystem;
|
|
176
|
+
const config = yield* ChangesetConfig;
|
|
177
|
+
const vanilla = yield* Effect.provide(PublishabilityDetector, PublishabilityDetectorLive);
|
|
178
|
+
return { detect: (pkg, root) => Effect.gen(function* () {
|
|
179
|
+
if (yield* config.isIgnored(pkg.name, root)) return [];
|
|
180
|
+
const mode = yield* config.mode(root);
|
|
181
|
+
if (mode === "none") return [];
|
|
182
|
+
if (mode === "silk") {
|
|
183
|
+
const raw = yield* readRaw(fs, pkg.packageJsonPath);
|
|
184
|
+
if (!raw) return [];
|
|
185
|
+
const binding = yield* readTargetsBinding(fs, pkg.path);
|
|
186
|
+
return SilkPublishability.detect(pkg.name, raw, binding);
|
|
187
|
+
}
|
|
188
|
+
return yield* vanilla.detect(pkg, root);
|
|
189
|
+
}) };
|
|
190
|
+
}));
|
|
191
|
+
|
|
192
|
+
//#endregion
|
|
193
|
+
export { PublishabilityDetectorAdaptiveLive, SilkPublishability, SilkPublishabilityDetectorLive, readTargetsBinding };
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { ChangesetConfigReader } from "./ChangesetConfigReader.js";
|
|
2
|
+
import { WorkspaceAnalysisError } from "../errors/WorkspaceAnalysisError.js";
|
|
3
|
+
import { AnalyzedWorkspace, WorkspaceAnalysis } from "../schemas/WorkspaceAnalysisSchemas.js";
|
|
4
|
+
import { ChangesetConfig } from "./ChangesetConfig.js";
|
|
5
|
+
import { SilkPublishability, readTargetsBinding } from "./SilkPublishability.js";
|
|
6
|
+
import { TagStrategy } from "./TagStrategy.js";
|
|
7
|
+
import { VersioningStrategy } from "./VersioningStrategy.js";
|
|
8
|
+
import { Context, Effect, Layer, Option } from "effect";
|
|
9
|
+
import { PackageManagerDetector, TopologicalSorter, WorkspaceDiscovery } from "workspaces-effect";
|
|
10
|
+
import { FileSystem } from "@effect/platform";
|
|
11
|
+
|
|
12
|
+
//#region src/services/SilkWorkspaceAnalyzer.ts
|
|
13
|
+
/**
|
|
14
|
+
* Service that performs a full workspace analysis — discovering packages,
|
|
15
|
+
* detecting publishability, computing versioning/tag strategies, and
|
|
16
|
+
* wiring up fixed/linked release groups.
|
|
17
|
+
*
|
|
18
|
+
* @remarks
|
|
19
|
+
* Orchestrates {@link WorkspaceDiscovery}, {@link PackageManagerDetector},
|
|
20
|
+
* {@link ChangesetConfigReader}, {@link VersioningStrategy}, and
|
|
21
|
+
* {@link TagStrategy} to produce a complete {@link WorkspaceAnalysis} for a
|
|
22
|
+
* given workspace root.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const result = await Effect.runPromise(
|
|
27
|
+
* Effect.gen(function* () {
|
|
28
|
+
* const analyzer = yield* SilkWorkspaceAnalyzer;
|
|
29
|
+
* return yield* analyzer.analyze("/path/to/monorepo");
|
|
30
|
+
* }).pipe(
|
|
31
|
+
* Effect.provide(SilkWorkspaceAnalyzerLive),
|
|
32
|
+
* // ... provide all transitive layers
|
|
33
|
+
* )
|
|
34
|
+
* );
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @since 0.2.0
|
|
38
|
+
*/
|
|
39
|
+
var SilkWorkspaceAnalyzer = class extends Context.Tag("@savvy-web/silk-effects/SilkWorkspaceAnalyzer")() {};
|
|
40
|
+
/**
|
|
41
|
+
* Read the raw package.json from disk as an untyped record.
|
|
42
|
+
*
|
|
43
|
+
* @remarks
|
|
44
|
+
* We read from disk rather than using WorkspacePackage.publishConfig because
|
|
45
|
+
* the upstream PublishConfig schema strips unknown fields (like Silk `targets`).
|
|
46
|
+
* SilkPublishability.detect needs the full raw publishConfig.
|
|
47
|
+
*/
|
|
48
|
+
const readRawPkgJson = (fs, packageJsonPath) => fs.readFileString(packageJsonPath).pipe(Effect.flatMap((content) => Effect.try({
|
|
49
|
+
try: () => JSON.parse(content),
|
|
50
|
+
catch: () => new WorkspaceAnalysisError({
|
|
51
|
+
root: packageJsonPath,
|
|
52
|
+
reason: "Invalid JSON in package.json"
|
|
53
|
+
})
|
|
54
|
+
})), Effect.mapError((err) => err instanceof WorkspaceAnalysisError ? err : new WorkspaceAnalysisError({
|
|
55
|
+
root: packageJsonPath,
|
|
56
|
+
reason: `Failed to read package.json: ${String(err)}`
|
|
57
|
+
})));
|
|
58
|
+
/**
|
|
59
|
+
* Determine the release status flags (versioned, tagged, released) for a workspace
|
|
60
|
+
* based on the changeset config.
|
|
61
|
+
*
|
|
62
|
+
* @param pkgName - The package name.
|
|
63
|
+
* @param isPrivate - Whether package.json has `private: true`.
|
|
64
|
+
* @param isPublishable - Whether the package has resolved publish targets.
|
|
65
|
+
* A package with `private: true` + `publishConfig.access` is publishable
|
|
66
|
+
* in Silk convention and should be versioned/tagged like a public package.
|
|
67
|
+
* @param config - The changeset config, or null if no changesets.
|
|
68
|
+
*/
|
|
69
|
+
function computeReleaseStatus(pkgName, isPrivate, isPublishable, config) {
|
|
70
|
+
if (config == null) return {
|
|
71
|
+
versioned: false,
|
|
72
|
+
tagged: false,
|
|
73
|
+
released: false
|
|
74
|
+
};
|
|
75
|
+
if ((config.ignore ?? []).some((p) => ChangesetConfig.matches(pkgName, p))) return {
|
|
76
|
+
versioned: false,
|
|
77
|
+
tagged: false,
|
|
78
|
+
released: false
|
|
79
|
+
};
|
|
80
|
+
if (!isPrivate || isPublishable) return {
|
|
81
|
+
versioned: true,
|
|
82
|
+
tagged: true,
|
|
83
|
+
released: true
|
|
84
|
+
};
|
|
85
|
+
const pp = config.privatePackages;
|
|
86
|
+
if (pp === void 0) return {
|
|
87
|
+
versioned: false,
|
|
88
|
+
tagged: false,
|
|
89
|
+
released: false
|
|
90
|
+
};
|
|
91
|
+
if (pp === false) return {
|
|
92
|
+
versioned: false,
|
|
93
|
+
tagged: false,
|
|
94
|
+
released: false
|
|
95
|
+
};
|
|
96
|
+
const versioned = pp.version === true;
|
|
97
|
+
const tagged = pp.tag === true;
|
|
98
|
+
return {
|
|
99
|
+
versioned,
|
|
100
|
+
tagged,
|
|
101
|
+
released: versioned && tagged
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Live implementation of {@link SilkWorkspaceAnalyzer}.
|
|
106
|
+
*
|
|
107
|
+
* @remarks
|
|
108
|
+
* Requires {@link WorkspaceDiscovery}, {@link PackageManagerDetector},
|
|
109
|
+
* {@link ChangesetConfigReader}, {@link VersioningStrategy}, and {@link TagStrategy}.
|
|
110
|
+
*
|
|
111
|
+
* @since 0.2.0
|
|
112
|
+
*/
|
|
113
|
+
const SilkWorkspaceAnalyzerLive = Layer.effect(SilkWorkspaceAnalyzer, Effect.gen(function* () {
|
|
114
|
+
const fs = yield* FileSystem.FileSystem;
|
|
115
|
+
const discovery = yield* WorkspaceDiscovery;
|
|
116
|
+
const sorter = yield* TopologicalSorter;
|
|
117
|
+
const pmDetector = yield* PackageManagerDetector;
|
|
118
|
+
const configReader = yield* ChangesetConfigReader;
|
|
119
|
+
const versioningStrategy = yield* VersioningStrategy;
|
|
120
|
+
const tagStrategy = yield* TagStrategy;
|
|
121
|
+
const analyze = (root) => Effect.gen(function* () {
|
|
122
|
+
const pm = yield* pmDetector.detect(root).pipe(Effect.mapError((err) => new WorkspaceAnalysisError({
|
|
123
|
+
root,
|
|
124
|
+
reason: `Package manager detection failed: ${String(err)}`
|
|
125
|
+
})));
|
|
126
|
+
const packages = yield* discovery.listPackages(root).pipe(Effect.mapError((err) => new WorkspaceAnalysisError({
|
|
127
|
+
root,
|
|
128
|
+
reason: `Workspace discovery failed: ${String(err)}`
|
|
129
|
+
})));
|
|
130
|
+
const topoOrder = yield* sorter.sort().pipe(Effect.mapError((err) => new WorkspaceAnalysisError({
|
|
131
|
+
root,
|
|
132
|
+
reason: `Cyclic dependency detected: ${String(err)}`
|
|
133
|
+
})));
|
|
134
|
+
const packagesByName = new Map(packages.map((p) => [p.name, p]));
|
|
135
|
+
const reordered = topoOrder.flatMap((name) => {
|
|
136
|
+
const pkg = packagesByName.get(name);
|
|
137
|
+
return pkg ? [pkg] : [];
|
|
138
|
+
});
|
|
139
|
+
const sortedPackages = reordered.length > 0 ? reordered : [...packages];
|
|
140
|
+
const changesetConfigOption = yield* configReader.read(root).pipe(Effect.option);
|
|
141
|
+
const changesetConfig = Option.getOrNull(changesetConfigOption);
|
|
142
|
+
const analyzedWorkspaces = [];
|
|
143
|
+
for (const pkg of sortedPackages) {
|
|
144
|
+
const pkgJson = yield* readRawPkgJson(fs, pkg.packageJsonPath);
|
|
145
|
+
const binding = yield* readTargetsBinding(fs, pkg.path);
|
|
146
|
+
const targets = SilkPublishability.detect(pkg.name, pkgJson, binding);
|
|
147
|
+
const isPublishable = targets.length > 0;
|
|
148
|
+
const isRoot = pkg.relativePath === ".";
|
|
149
|
+
const { versioned, tagged, released } = computeReleaseStatus(pkg.name, pkg.private, isPublishable, changesetConfig);
|
|
150
|
+
const analyzed = new AnalyzedWorkspace({
|
|
151
|
+
name: pkg.name,
|
|
152
|
+
version: { current: pkg.version },
|
|
153
|
+
path: pkg.path,
|
|
154
|
+
root: isRoot,
|
|
155
|
+
publishConfig: null,
|
|
156
|
+
publishable: isPublishable,
|
|
157
|
+
targets: [...targets],
|
|
158
|
+
versioned,
|
|
159
|
+
tagged,
|
|
160
|
+
released,
|
|
161
|
+
linked: [],
|
|
162
|
+
fixed: []
|
|
163
|
+
});
|
|
164
|
+
analyzedWorkspaces.push(analyzed);
|
|
165
|
+
}
|
|
166
|
+
if (changesetConfig) {
|
|
167
|
+
const fixedGroups = changesetConfig.fixed ?? [];
|
|
168
|
+
const linkedGroups = changesetConfig.linked ?? [];
|
|
169
|
+
const fixedByName = /* @__PURE__ */ new Map();
|
|
170
|
+
for (const group of fixedGroups) {
|
|
171
|
+
const members = analyzedWorkspaces.filter((w) => group.includes(w.name));
|
|
172
|
+
for (const member of members) fixedByName.set(member.name, members.filter((m) => m !== member));
|
|
173
|
+
}
|
|
174
|
+
const linkedByName = /* @__PURE__ */ new Map();
|
|
175
|
+
for (const group of linkedGroups) {
|
|
176
|
+
const members = analyzedWorkspaces.filter((w) => group.includes(w.name));
|
|
177
|
+
for (const member of members) linkedByName.set(member.name, members.filter((m) => m !== member));
|
|
178
|
+
}
|
|
179
|
+
for (let i = 0; i < analyzedWorkspaces.length; i++) {
|
|
180
|
+
const ws = analyzedWorkspaces[i];
|
|
181
|
+
const fixedRefs = fixedByName.get(ws.name) ?? [];
|
|
182
|
+
const linkedRefs = linkedByName.get(ws.name) ?? [];
|
|
183
|
+
if (fixedRefs.length > 0 || linkedRefs.length > 0) analyzedWorkspaces[i] = new AnalyzedWorkspace({
|
|
184
|
+
...ws,
|
|
185
|
+
fixed: fixedRefs,
|
|
186
|
+
linked: linkedRefs
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const publishableNames = analyzedWorkspaces.filter((w) => w.publishable).map((w) => w.name);
|
|
191
|
+
const versioning = yield* versioningStrategy.detect(publishableNames, root).pipe(Effect.mapError((err) => new WorkspaceAnalysisError({
|
|
192
|
+
root,
|
|
193
|
+
reason: `Versioning strategy detection failed: ${String(err)}`
|
|
194
|
+
})));
|
|
195
|
+
const tagStrategyType = yield* tagStrategy.determine(versioning);
|
|
196
|
+
return new WorkspaceAnalysis({
|
|
197
|
+
root,
|
|
198
|
+
runtime: pm.runtime,
|
|
199
|
+
packageManager: {
|
|
200
|
+
type: pm.type,
|
|
201
|
+
version: pm.version
|
|
202
|
+
},
|
|
203
|
+
workspaces: analyzedWorkspaces,
|
|
204
|
+
changesetConfig,
|
|
205
|
+
versioning,
|
|
206
|
+
tagStrategy: tagStrategyType
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
return { analyze };
|
|
210
|
+
}));
|
|
211
|
+
|
|
212
|
+
//#endregion
|
|
213
|
+
export { SilkWorkspaceAnalyzer, SilkWorkspaceAnalyzerLive };
|