@savvy-web/silk-effects 3.3.1 → 4.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/changesets/changelog/getReleaseLine.js +1 -1
- package/changesets/constants.js +0 -18
- package/changesets/index.js +10 -24
- package/changesets/schemas/changeset.js +8 -4
- package/changesets/schemas/dependency-table.js +15 -4
- package/changesets/schemas/git.js +7 -2
- package/changesets/schemas/github.js +4 -4
- package/changesets/schemas/options.js +3 -3
- package/changesets/schemas/package-scope.js +2 -5
- package/changesets/schemas/primitives.js +2 -2
- package/changesets/schemas/release-plan.js +12 -8
- package/changesets/schemas/version-files.js +4 -4
- package/changesets/services/branch-analyzer.js +60 -191
- package/changesets/services/changelog.js +2 -15
- package/changesets/services/config-inspector.js +109 -75
- package/changesets/services/deps-regen.js +58 -32
- package/changesets/services/github.js +3 -16
- package/changesets/services/maintenance-reason.js +5 -1
- package/changesets/services/markdown.js +3 -16
- package/changesets/services/release-planner.js +4 -13
- package/changesets/utils/commit-parser.js +0 -18
- package/changesets/utils/dep-diff.js +0 -27
- package/changesets/utils/git.js +15 -50
- package/changesets/utils/issue-refs.js +0 -13
- package/changesets/utils/logger.js +0 -13
- package/changesets/utils/markdown-link.js +0 -14
- package/changesets/utils/publishability.js +11 -24
- package/changesets/utils/strip-frontmatter.js +0 -19
- package/changesets/utils/version-files.js +85 -51
- package/commitlint/config/schema.js +9 -5
- package/commitlint/detection/scopes.js +2 -7
- package/commitlint/formatter/messages.js +0 -5
- package/commitlint/hook/diagnostics/branch.js +12 -12
- package/commitlint/hook/diagnostics/open-issues.js +13 -7
- package/commitlint/hook/diagnostics/signing.js +30 -37
- package/commitlint/hook/envelope.js +2 -8
- package/commitlint/hook/silence-logger.js +14 -12
- package/index.d.ts +897 -932
- package/lint/cli/templates/markdownlint.gen.js +0 -9
- package/lint/handlers/PnpmWorkspace.js +26 -12
- package/lint/utils/Filter.js +0 -13
- package/lint/utils/Workspace.js +10 -6
- package/package.json +11 -10
- package/repos/index.js +3 -5
- package/repos/schemas/manifest.js +7 -13
- package/repos/schemas/reports.js +5 -1
- package/repos/services/config-store.js +6 -10
- package/repos/services/manager.js +52 -112
- package/schemas/CommentStyle.js +1 -1
- package/schemas/ResolvedTool.js +52 -17
- package/schemas/SectionBlock.js +1 -1
- package/schemas/SectionDefinition.js +2 -2
- package/schemas/TagStrategySchemas.js +1 -1
- package/schemas/ToolDefinition.js +1 -1
- package/schemas/ToolResults.js +1 -1
- package/schemas/VersioningSchemas.js +28 -9
- package/schemas/WorkspaceAnalysisSchemas.js +27 -17
- package/services/BiomeSchemaSync.js +7 -8
- package/services/ChangesetConfig.js +2 -2
- package/services/ChangesetConfigReader.js +7 -8
- package/services/ConfigDiscovery.js +5 -6
- package/services/ManagedSection.js +3 -4
- package/services/SilkPublishability.js +38 -15
- package/services/SilkWorkspaceAnalyzer.js +7 -9
- package/services/TagStrategy.js +3 -3
- package/services/ToolDiscovery.js +22 -21
- package/services/VersioningStrategy.js +2 -2
- package/tsdoc-metadata.json +1 -1
- package/turbo/schemas/DryRun.js +8 -14
- package/turbo/schemas/results.js +8 -8
- package/turbo/services/TurboInspector.js +30 -23
- package/utils/ToolCommand.js +38 -12
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
//#region src/lint/cli/templates/markdownlint.gen.ts
|
|
2
|
-
/**
|
|
3
|
-
* Markdownlint template data.
|
|
4
|
-
*
|
|
5
|
-
* Originally generated in the now-deprecated `@savvy-web/lint-staged` package;
|
|
6
|
-
* the generator does not live in this repo, so this file is maintained by hand.
|
|
7
|
-
* Edit the `MARKDOWNLINT_TEMPLATE` / `MARKDOWNLINT_CONFIG` objects directly.
|
|
8
|
-
*
|
|
9
|
-
* @internal
|
|
10
|
-
*/
|
|
11
2
|
/** Full markdownlint-cli2 config template. */
|
|
12
3
|
const MARKDOWNLINT_TEMPLATE = {
|
|
13
4
|
$schema: "https://raw.githubusercontent.com/DavidAnson/markdownlint-cli2/v0.22.0/schema/markdownlint-cli2-config-schema.json",
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Command } from "../utils/Command.js";
|
|
2
|
+
import { Effect, Result } from "effect";
|
|
2
3
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
-
import {
|
|
4
|
+
import { Yaml, YamlStringifyOptions } from "@effected/yaml";
|
|
5
|
+
import { format, resolveConfig } from "prettier";
|
|
4
6
|
|
|
5
7
|
//#region src/lint/handlers/PnpmWorkspace.ts
|
|
6
8
|
/**
|
|
@@ -10,12 +12,21 @@ import { parse, stringify } from "yaml";
|
|
|
10
12
|
*/
|
|
11
13
|
/**
|
|
12
14
|
* Default YAML stringify options for consistent formatting.
|
|
15
|
+
*
|
|
16
|
+
* @remarks
|
|
17
|
+
* `lineWidth: 0` disables line wrapping, matching the v3 `yaml` package
|
|
18
|
+
* behavior. `@effected/yaml` emits block sequences without the extra
|
|
19
|
+
* two-space indentation the v3 `yaml` package used, so the stringified
|
|
20
|
+
* output is normalized through Prettier's YAML printer below — probed
|
|
21
|
+
* byte-identical to the v3 `yaml.stringify(..., { indent: 2, lineWidth: 0,
|
|
22
|
+
* singleQuote: false })` output on this repo's real `pnpm-workspace.yaml`
|
|
23
|
+
* and on hostile synthetic shapes (quoted keys, block scalars, nested
|
|
24
|
+
* sequences of maps).
|
|
13
25
|
*/
|
|
14
|
-
const DEFAULT_STRINGIFY_OPTIONS = {
|
|
26
|
+
const DEFAULT_STRINGIFY_OPTIONS = new YamlStringifyOptions({
|
|
15
27
|
indent: 2,
|
|
16
|
-
lineWidth: 0
|
|
17
|
-
|
|
18
|
-
};
|
|
28
|
+
lineWidth: 0
|
|
29
|
+
});
|
|
19
30
|
/**
|
|
20
31
|
* Handler for pnpm-workspace.yaml.
|
|
21
32
|
*
|
|
@@ -116,20 +127,23 @@ var PnpmWorkspace = class PnpmWorkspace {
|
|
|
116
127
|
const skipSort = options.skipSort ?? false;
|
|
117
128
|
const skipFormat = options.skipFormat ?? false;
|
|
118
129
|
const skipLint = options.skipLint ?? false;
|
|
119
|
-
return () => {
|
|
130
|
+
return async () => {
|
|
120
131
|
const filepath = "pnpm-workspace.yaml";
|
|
121
132
|
if (!existsSync(filepath)) return [];
|
|
122
133
|
const content = readFileSync(filepath, "utf-8");
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
} catch (error) {
|
|
127
|
-
if (!skipLint) throw new Error(`Invalid YAML in ${filepath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
134
|
+
const parseResult = Effect.runSync(Effect.result(Yaml.parse(content)));
|
|
135
|
+
if (Result.isFailure(parseResult)) {
|
|
136
|
+
if (!skipLint) throw new Error(`Invalid YAML in ${filepath}: ${parseResult.failure.message}`);
|
|
128
137
|
return [];
|
|
129
138
|
}
|
|
139
|
+
let parsed = parseResult.success;
|
|
130
140
|
if (!skipSort) parsed = PnpmWorkspace.sortContent(parsed);
|
|
131
141
|
if (!skipSort || !skipFormat) {
|
|
132
|
-
writeFileSync(filepath, stringify(parsed, DEFAULT_STRINGIFY_OPTIONS),
|
|
142
|
+
writeFileSync(filepath, await format(Effect.runSync(Yaml.stringify(parsed, DEFAULT_STRINGIFY_OPTIONS)), {
|
|
143
|
+
...await resolveConfig(filepath),
|
|
144
|
+
filepath,
|
|
145
|
+
parser: "yaml"
|
|
146
|
+
}), "utf-8");
|
|
133
147
|
return [];
|
|
134
148
|
}
|
|
135
149
|
return [];
|
package/lint/utils/Filter.js
CHANGED
|
@@ -1,18 +1,5 @@
|
|
|
1
1
|
//#region src/lint/utils/Filter.ts
|
|
2
2
|
/**
|
|
3
|
-
* Utilities for filtering staged file lists.
|
|
4
|
-
*
|
|
5
|
-
* @example
|
|
6
|
-
* ```typescript
|
|
7
|
-
* import { Filter } from '@savvy-web/silk/lint';
|
|
8
|
-
*
|
|
9
|
-
* const handler = (filenames: readonly string[]) => {
|
|
10
|
-
* const filtered = Filter.exclude(filenames, ['dist/', '__fixtures__']);
|
|
11
|
-
* return filtered.length > 0 ? `biome check ${Filter.shellEscape(filtered)}` : [];
|
|
12
|
-
* };
|
|
13
|
-
* ```
|
|
14
|
-
*/
|
|
15
|
-
/**
|
|
16
3
|
* Static utility class for filtering file lists.
|
|
17
4
|
*/
|
|
18
5
|
var Filter = class Filter {
|
package/lint/utils/Workspace.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { dirname } from "node:path";
|
|
2
|
-
import { findWorkspaceRootSync, getWorkspacePackagesSync } from "workspaces
|
|
2
|
+
import { findWorkspaceRootSync, getWorkspacePackagesSync } from "@effected/workspaces";
|
|
3
|
+
import { nodeSyncOps } from "@effected/workspaces/node-sync";
|
|
3
4
|
|
|
4
5
|
//#region src/lint/utils/Workspace.ts
|
|
5
6
|
/**
|
|
6
7
|
* Workspace-aware discovery utilities.
|
|
7
8
|
*
|
|
8
9
|
* @remarks
|
|
9
|
-
* Wraps the synchronous APIs from
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* Wraps the synchronous APIs from `@effected/workspaces` with caching.
|
|
11
|
+
* The kit's sync entry points never import `node:*` themselves — the
|
|
12
|
+
* Node binding is the kit's own `@effected/workspaces/node-sync` preset
|
|
13
|
+
* (`nodeSyncOps`), which replaced this module's hand-wired ops in kit
|
|
14
|
+
* round 3. Workspace layout does not change during a lint-staged run,
|
|
15
|
+
* so results are cached on first access. Use `resetWorkspaceCache()`
|
|
12
16
|
* in tests to clear state between runs.
|
|
13
17
|
*/
|
|
14
18
|
/** Sentinel indicating "not yet resolved". */
|
|
@@ -23,7 +27,7 @@ let cachedPaths = UNRESOLVED;
|
|
|
23
27
|
*/
|
|
24
28
|
function getWorkspaceRoot() {
|
|
25
29
|
if (cachedRoot !== UNRESOLVED) return cachedRoot;
|
|
26
|
-
cachedRoot = findWorkspaceRootSync()
|
|
30
|
+
cachedRoot = findWorkspaceRootSync(nodeSyncOps);
|
|
27
31
|
return cachedRoot;
|
|
28
32
|
}
|
|
29
33
|
/**
|
|
@@ -38,7 +42,7 @@ function getWorkspacePackages() {
|
|
|
38
42
|
cachedPackages = null;
|
|
39
43
|
return null;
|
|
40
44
|
}
|
|
41
|
-
cachedPackages =
|
|
45
|
+
cachedPackages = getWorkspacePackagesSync(root, nodeSyncOps).filter((pkg) => pkg.path !== root);
|
|
42
46
|
return cachedPackages;
|
|
43
47
|
}
|
|
44
48
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@savvy-web/silk-effects",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Shared Effect library for Silk Suite conventions",
|
|
6
6
|
"homepage": "https://github.com/savvy-web/systems/tree/main/packages/silk-effects",
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"exports": {
|
|
24
24
|
".": {
|
|
25
25
|
"types": "./index.d.ts",
|
|
26
|
-
"import": "./index.js"
|
|
26
|
+
"import": "./index.js",
|
|
27
|
+
"default": "./index.js"
|
|
27
28
|
},
|
|
28
29
|
"./package.json": "./package.json"
|
|
29
30
|
},
|
|
@@ -32,28 +33,28 @@
|
|
|
32
33
|
"@changesets/config": "^4.0.0-next.6",
|
|
33
34
|
"@changesets/get-github-info": "^1.0.0-next.3",
|
|
34
35
|
"@changesets/get-release-plan": "^5.0.0-next.7",
|
|
36
|
+
"@effected/git": "^0.4.0",
|
|
37
|
+
"@effected/glob": "^0.1.1",
|
|
38
|
+
"@effected/jsonc": "^0.2.0",
|
|
39
|
+
"@effected/package-json": "^0.3.0",
|
|
40
|
+
"@effected/walker": "^0.2.1",
|
|
41
|
+
"@effected/workspaces": "^0.3.1",
|
|
42
|
+
"@effected/yaml": "^0.3.0",
|
|
35
43
|
"@manypkg/get-packages": "^3.1.0",
|
|
36
|
-
"jsonc-effect": "^0.3.1",
|
|
37
44
|
"mdast-util-heading-range": "^4.0.0",
|
|
38
45
|
"mdast-util-to-string": "^4.0.0",
|
|
39
46
|
"prettier": "^3.9.5",
|
|
40
47
|
"remark-gfm": "^4.0.1",
|
|
41
48
|
"remark-parse": "^11.0.0",
|
|
42
49
|
"remark-stringify": "^11.0.0",
|
|
43
|
-
"semver-effect": "^0.3.1",
|
|
44
50
|
"shell-quote": "^1.10.0",
|
|
45
51
|
"sort-package-json": "^4.0.0",
|
|
46
|
-
"tinyglobby": "^0.2.17",
|
|
47
52
|
"unified": "^11.0.5",
|
|
48
53
|
"unified-lint-rule": "^3.0.1",
|
|
49
54
|
"unist-util-visit": "^5.1.0",
|
|
50
|
-
"workspaces-effect": "^2.1.0",
|
|
51
|
-
"yaml": "^2.9.0",
|
|
52
|
-
"yaml-effect": "^0.7.2",
|
|
53
55
|
"yaml-lint": "^1.7.0"
|
|
54
56
|
},
|
|
55
57
|
"peerDependencies": {
|
|
56
|
-
"
|
|
57
|
-
"effect": "^3.21.0"
|
|
58
|
+
"effect": "4.0.0-beta.98"
|
|
58
59
|
}
|
|
59
60
|
}
|
package/repos/index.js
CHANGED
|
@@ -3,8 +3,8 @@ import { MANIFEST_PATH, REPOS_DIR } from "./constants.js";
|
|
|
3
3
|
import { GitSubmoduleError, GitSubmoduleErrorBase, NoteNotFoundError, NoteNotFoundErrorBase, RepoNotFoundError, RepoNotFoundErrorBase, ReposConfigError, ReposConfigErrorBase } from "./errors.js";
|
|
4
4
|
import { RepoEntry, RepoName, RepoNote, RepoOrientation, ReposManifestFile } from "./schemas/manifest.js";
|
|
5
5
|
import { RepoStatusEntry, ReposAddResult, ReposNoteResult, ReposPinResult, ReposStatusReport, ReposSyncReport } from "./schemas/reports.js";
|
|
6
|
-
import { ReposConfigStore,
|
|
7
|
-
import { ReposManager,
|
|
6
|
+
import { ReposConfigStore, ReposConfigStoreLive } from "./services/config-store.js";
|
|
7
|
+
import { ReposManager, ReposManagerLive, STALE_LOCK_MAX_AGE_MS } from "./services/manager.js";
|
|
8
8
|
|
|
9
9
|
//#region src/repos/index.ts
|
|
10
10
|
var repos_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -26,10 +26,8 @@ var repos_exports = /* @__PURE__ */ __exportAll({
|
|
|
26
26
|
ReposConfigError: () => ReposConfigError,
|
|
27
27
|
ReposConfigErrorBase: () => ReposConfigErrorBase,
|
|
28
28
|
ReposConfigStore: () => ReposConfigStore,
|
|
29
|
-
ReposConfigStoreBase: () => ReposConfigStoreBase,
|
|
30
29
|
ReposConfigStoreLive: () => ReposConfigStoreLive,
|
|
31
30
|
ReposManager: () => ReposManager,
|
|
32
|
-
ReposManagerBase: () => ReposManagerBase,
|
|
33
31
|
ReposManagerLive: () => ReposManagerLive,
|
|
34
32
|
ReposManifestFile: () => ReposManifestFile,
|
|
35
33
|
ReposNoteResult: () => ReposNoteResult,
|
|
@@ -40,4 +38,4 @@ var repos_exports = /* @__PURE__ */ __exportAll({
|
|
|
40
38
|
});
|
|
41
39
|
|
|
42
40
|
//#endregion
|
|
43
|
-
export { GitSubmoduleError, GitSubmoduleErrorBase, MANIFEST_PATH, NoteNotFoundError, NoteNotFoundErrorBase, REPOS_DIR, RepoEntry, RepoName, RepoNotFoundError, RepoNotFoundErrorBase, RepoNote, RepoOrientation, RepoStatusEntry, ReposAddResult, ReposConfigError, ReposConfigErrorBase, ReposConfigStore,
|
|
41
|
+
export { GitSubmoduleError, GitSubmoduleErrorBase, MANIFEST_PATH, NoteNotFoundError, NoteNotFoundErrorBase, REPOS_DIR, RepoEntry, RepoName, RepoNotFoundError, RepoNotFoundErrorBase, RepoNote, RepoOrientation, RepoStatusEntry, ReposAddResult, ReposConfigError, ReposConfigErrorBase, ReposConfigStore, ReposConfigStoreLive, ReposManager, ReposManagerLive, ReposManifestFile, ReposNoteResult, ReposPinResult, ReposStatusReport, ReposSyncReport, STALE_LOCK_MAX_AGE_MS, repos_exports };
|
|
@@ -7,7 +7,7 @@ import { Schema } from "effect";
|
|
|
7
7
|
* (`.repos/<name>`) without escaping the `.repos/` directory.
|
|
8
8
|
* @public
|
|
9
9
|
*/
|
|
10
|
-
const RepoName = Schema.String.
|
|
10
|
+
const RepoName = Schema.String.check(Schema.isPattern(/^(?!\.{1,2}$)[A-Za-z0-9][A-Za-z0-9._-]*$/)).annotate({ identifier: "RepoName" });
|
|
11
11
|
/**
|
|
12
12
|
* An agent-appended note: a discovered answer stamped with the pin it was written against.
|
|
13
13
|
* @public
|
|
@@ -16,7 +16,7 @@ const RepoNote = Schema.Struct({
|
|
|
16
16
|
id: Schema.String,
|
|
17
17
|
date: Schema.String,
|
|
18
18
|
ref: Schema.String,
|
|
19
|
-
note: Schema.String.
|
|
19
|
+
note: Schema.String.check(Schema.isMinLength(1))
|
|
20
20
|
});
|
|
21
21
|
/**
|
|
22
22
|
* Curated per-repo orientation: layout, entry points, where to start.
|
|
@@ -24,10 +24,7 @@ const RepoNote = Schema.Struct({
|
|
|
24
24
|
*/
|
|
25
25
|
const RepoOrientation = Schema.Struct({
|
|
26
26
|
layout: Schema.optional(Schema.String),
|
|
27
|
-
keyPaths: Schema.optional(Schema.Record(
|
|
28
|
-
key: Schema.String,
|
|
29
|
-
value: Schema.String
|
|
30
|
-
})),
|
|
27
|
+
keyPaths: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
|
31
28
|
startHere: Schema.optional(Schema.String)
|
|
32
29
|
});
|
|
33
30
|
/**
|
|
@@ -35,9 +32,9 @@ const RepoOrientation = Schema.Struct({
|
|
|
35
32
|
* @public
|
|
36
33
|
*/
|
|
37
34
|
const RepoEntry = Schema.Struct({
|
|
38
|
-
url: Schema.String.
|
|
39
|
-
ref: Schema.String.
|
|
40
|
-
purpose: Schema.String.
|
|
35
|
+
url: Schema.String.check(Schema.isMinLength(1)),
|
|
36
|
+
ref: Schema.String.check(Schema.isMinLength(1)),
|
|
37
|
+
purpose: Schema.String.check(Schema.isMinLength(1)),
|
|
41
38
|
sparse: Schema.optional(Schema.Array(Schema.String)),
|
|
42
39
|
orientation: Schema.optional(RepoOrientation),
|
|
43
40
|
notes: Schema.optional(Schema.Array(RepoNote))
|
|
@@ -61,10 +58,7 @@ const isRepoName = Schema.is(RepoName);
|
|
|
61
58
|
* The committed .repos/config.json manifest.
|
|
62
59
|
* @public
|
|
63
60
|
*/
|
|
64
|
-
const ReposManifestFile = Schema.Struct({ repos: Schema.Record({
|
|
65
|
-
key: Schema.String,
|
|
66
|
-
value: RepoEntry
|
|
67
|
-
}).pipe(Schema.filter((repos) => {
|
|
61
|
+
const ReposManifestFile = Schema.Struct({ repos: Schema.Record(Schema.String, RepoEntry).check(Schema.makeFilter((repos) => {
|
|
68
62
|
const badKey = Object.keys(repos).find((key) => !isRepoName(key));
|
|
69
63
|
return badKey === void 0 ? void 0 : `every repos key must be a valid RepoName (non-empty, no "/" or "\\", not "." or ".."); got ${JSON.stringify(badKey)}`;
|
|
70
64
|
})) });
|
package/repos/schemas/reports.js
CHANGED
|
@@ -62,7 +62,11 @@ const ReposAddResult = Schema.Struct({
|
|
|
62
62
|
*/
|
|
63
63
|
const ReposNoteResult = Schema.Struct({
|
|
64
64
|
name: Schema.String,
|
|
65
|
-
op: Schema.
|
|
65
|
+
op: Schema.Literals([
|
|
66
|
+
"add",
|
|
67
|
+
"remove",
|
|
68
|
+
"promote"
|
|
69
|
+
]),
|
|
66
70
|
id: Schema.String,
|
|
67
71
|
noteCount: Schema.Number
|
|
68
72
|
});
|
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
import { MANIFEST_PATH, REPOS_DIR } from "../constants.js";
|
|
2
2
|
import { ReposConfigError } from "../errors.js";
|
|
3
3
|
import { ReposManifestFile } from "../schemas/manifest.js";
|
|
4
|
-
import { Context, Effect, Layer, Schema } from "effect";
|
|
5
|
-
import { FileSystem, Path } from "@effect/platform";
|
|
4
|
+
import { Context, Effect, FileSystem, Layer, Path, Schema } from "effect";
|
|
6
5
|
|
|
7
6
|
//#region src/repos/services/config-store.ts
|
|
8
|
-
const _tag = Context.Tag("@savvy-web/silk-effects/ReposConfigStore");
|
|
9
|
-
/** @internal */
|
|
10
|
-
const ReposConfigStoreBase = _tag();
|
|
11
7
|
/**
|
|
12
8
|
* Reads, validates, and writes the .repos/config.json manifest.
|
|
13
9
|
* @public
|
|
14
10
|
*/
|
|
15
|
-
var ReposConfigStore = class extends
|
|
11
|
+
var ReposConfigStore = class extends Context.Service()("@savvy-web/silk-effects/ReposConfigStore") {};
|
|
16
12
|
/**
|
|
17
|
-
* Live layer over the
|
|
13
|
+
* Live layer over the core FileSystem.
|
|
18
14
|
* @public
|
|
19
15
|
*/
|
|
20
16
|
const ReposConfigStoreLive = Layer.effect(ReposConfigStore, Effect.gen(function* () {
|
|
@@ -45,7 +41,7 @@ const ReposConfigStoreLive = Layer.effect(ReposConfigStore, Effect.gen(function*
|
|
|
45
41
|
kind: "invalid"
|
|
46
42
|
})
|
|
47
43
|
});
|
|
48
|
-
return yield* Schema.
|
|
44
|
+
return yield* Schema.decodeUnknownEffect(ReposManifestFile)(json).pipe(Effect.mapError((cause) => new ReposConfigError({
|
|
49
45
|
path: manifestPath(root),
|
|
50
46
|
reason: String(cause),
|
|
51
47
|
kind: "invalid"
|
|
@@ -58,7 +54,7 @@ const ReposConfigStoreLive = Layer.effect(ReposConfigStore, Effect.gen(function*
|
|
|
58
54
|
reason: `mkdir failed: ${String(cause)}`,
|
|
59
55
|
kind: "invalid"
|
|
60
56
|
})));
|
|
61
|
-
const encoded = yield* Schema.
|
|
57
|
+
const encoded = yield* Schema.encodeEffect(ReposManifestFile)(manifest).pipe(Effect.mapError((cause) => new ReposConfigError({
|
|
62
58
|
path: manifestPath(root),
|
|
63
59
|
reason: String(cause),
|
|
64
60
|
kind: "invalid"
|
|
@@ -83,4 +79,4 @@ const ReposConfigStoreLive = Layer.effect(ReposConfigStore, Effect.gen(function*
|
|
|
83
79
|
}));
|
|
84
80
|
|
|
85
81
|
//#endregion
|
|
86
|
-
export { ReposConfigStore,
|
|
82
|
+
export { ReposConfigStore, ReposConfigStoreLive };
|
|
@@ -2,8 +2,8 @@ import { MANIFEST_PATH, REPOS_DIR } from "../constants.js";
|
|
|
2
2
|
import { GitSubmoduleError, NoteNotFoundError, RepoNotFoundError, ReposConfigError } from "../errors.js";
|
|
3
3
|
import { RepoName } from "../schemas/manifest.js";
|
|
4
4
|
import { ReposConfigStore } from "./config-store.js";
|
|
5
|
-
import { Clock, Context, Effect, Layer, Option,
|
|
6
|
-
import {
|
|
5
|
+
import { Clock, Context, Effect, FileSystem, Layer, Option, Path, Schema } from "effect";
|
|
6
|
+
import { Git } from "@effected/git";
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
8
|
|
|
9
9
|
//#region src/repos/services/manager.ts
|
|
@@ -25,9 +25,6 @@ const STALE_LOCKS = ["index.lock", "shallow.lock"];
|
|
|
25
25
|
* @public
|
|
26
26
|
*/
|
|
27
27
|
const STALE_LOCK_MAX_AGE_MS = 10 * 6e4;
|
|
28
|
-
const _tag = Context.Tag("@savvy-web/silk-effects/ReposManager");
|
|
29
|
-
/** @internal */
|
|
30
|
-
const ReposManagerBase = _tag();
|
|
31
28
|
/**
|
|
32
29
|
* Drives the vendored `.repos/` submodules over git: reports status
|
|
33
30
|
* (presence, dirtiness, stale notes), reconciles the working tree with the
|
|
@@ -35,54 +32,38 @@ const ReposManagerBase = _tag();
|
|
|
35
32
|
* ref (`pin`), and adds, removes, or promotes agent notes (`note`).
|
|
36
33
|
* @public
|
|
37
34
|
*/
|
|
38
|
-
var ReposManager = class extends
|
|
35
|
+
var ReposManager = class extends Context.Service()("@savvy-web/silk-effects/ReposManager") {};
|
|
39
36
|
/**
|
|
40
37
|
* Live implementation of {@link ReposManager}.
|
|
41
38
|
*
|
|
42
39
|
* @remarks
|
|
43
|
-
*
|
|
44
|
-
* construction
|
|
45
|
-
*
|
|
40
|
+
* All git plumbing runs through `@effected/git`'s `Git` service (captured once
|
|
41
|
+
* at layer construction; `Git.layer` requires `ChildProcessSpawner` at the app
|
|
42
|
+
* edge), so the public method effects stay at `R = never`. The kit classifies
|
|
43
|
+
* git's exit-code/stderr taxonomy into typed failures, which are mapped onto
|
|
44
|
+
* this module's {@link GitSubmoduleError} to keep the declared error unions.
|
|
46
45
|
* @public
|
|
47
46
|
*/
|
|
48
47
|
const ReposManagerLive = Layer.effect(ReposManager, Effect.gen(function* () {
|
|
49
48
|
const configStore = yield* ReposConfigStore;
|
|
50
49
|
const fs = yield* FileSystem.FileSystem;
|
|
51
50
|
const path = yield* Path.Path;
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
process.exitCode,
|
|
57
|
-
process.stdout.pipe(Stream.decodeText(), Stream.runFold("", (acc, chunk) => acc + chunk)),
|
|
58
|
-
process.stderr.pipe(Stream.decodeText(), Stream.runFold("", (acc, chunk) => acc + chunk))
|
|
59
|
-
], { concurrency: 3 });
|
|
60
|
-
if (exitCode !== 0) return yield* Effect.fail(new GitSubmoduleError({
|
|
61
|
-
command: `git ${args.join(" ")}`,
|
|
62
|
-
cwd,
|
|
63
|
-
reason: stderr.trim().length > 0 ? stderr.trim() : `exit code ${exitCode}`
|
|
64
|
-
}));
|
|
65
|
-
return stdout.trim();
|
|
66
|
-
})).pipe(Effect.catchAll((cause) => Effect.fail(cause instanceof GitSubmoduleError ? cause : new GitSubmoduleError({
|
|
67
|
-
command: `git ${args.join(" ")}`,
|
|
51
|
+
const git = yield* Git;
|
|
52
|
+
/** Map any typed `@effected/git` failure onto this module's `GitSubmoduleError`. */
|
|
53
|
+
const asSubmoduleError = (command, cwd) => (error) => new GitSubmoduleError({
|
|
54
|
+
command,
|
|
68
55
|
cwd,
|
|
69
|
-
reason:
|
|
70
|
-
})
|
|
56
|
+
reason: error.message
|
|
57
|
+
});
|
|
71
58
|
const isPresent = (repoPath) => fs.readDirectory(repoPath).pipe(Effect.map((files) => files.length > 0), Effect.orElseSucceed(() => false));
|
|
72
59
|
const status = (root) => Effect.gen(function* () {
|
|
73
60
|
const manifest = yield* configStore.read(root);
|
|
74
61
|
const repos = yield* Effect.forEach(Object.entries(manifest.repos), ([name, entry]) => Effect.gen(function* () {
|
|
75
62
|
const repoPath = path.join(root, REPOS_DIR, name);
|
|
76
63
|
const present = yield* isPresent(repoPath);
|
|
77
|
-
const
|
|
78
|
-
"ls-tree",
|
|
79
|
-
"HEAD",
|
|
80
|
-
"--",
|
|
81
|
-
`${REPOS_DIR}/${name}`
|
|
82
|
-
]);
|
|
83
|
-
const commit = lsTree.length > 0 ? lsTree.split(/\s+/)[2] ?? null : null;
|
|
64
|
+
const commit = (yield* git.lsTree(root, "HEAD", { pathspec: [`${".repos"}/${name}`] }).pipe(Effect.mapError(asSubmoduleError(`git ls-tree HEAD -- ${".repos"}/${name}`, root))))[0]?.oid ?? null;
|
|
84
65
|
let dirty = false;
|
|
85
|
-
if (present) dirty = (yield*
|
|
66
|
+
if (present) dirty = (yield* git.status(repoPath).pipe(Effect.mapError(asSubmoduleError("git status --porcelain", repoPath)))).length > 0;
|
|
86
67
|
const staleNoteIds = (entry.notes ?? []).filter((note) => note.ref !== entry.ref).map((note) => note.id);
|
|
87
68
|
return {
|
|
88
69
|
name,
|
|
@@ -123,24 +104,15 @@ const ReposManagerLive = Layer.effect(ReposManager, Effect.gen(function* () {
|
|
|
123
104
|
}
|
|
124
105
|
if (clearedAnyLock) clearedLocks.push(name);
|
|
125
106
|
if (!(yield* isPresent(repoPath))) {
|
|
126
|
-
yield*
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
"1",
|
|
132
|
-
"--",
|
|
133
|
-
`${REPOS_DIR}/${name}`
|
|
134
|
-
]);
|
|
107
|
+
yield* git.submoduleUpdate(root, {
|
|
108
|
+
init: true,
|
|
109
|
+
depth: 1,
|
|
110
|
+
paths: [`${REPOS_DIR}/${name}`]
|
|
111
|
+
}).pipe(Effect.mapError(asSubmoduleError(`git submodule update --init --depth 1 -- ${REPOS_DIR}/${name}`, root)));
|
|
135
112
|
initialized.push(name);
|
|
136
113
|
} else upToDate.push(name);
|
|
137
114
|
if (entry.sparse && entry.sparse.length > 0) {
|
|
138
|
-
yield*
|
|
139
|
-
"sparse-checkout",
|
|
140
|
-
"set",
|
|
141
|
-
"--no-cone",
|
|
142
|
-
...entry.sparse
|
|
143
|
-
]);
|
|
115
|
+
yield* git.sparseCheckoutSet(repoPath, entry.sparse, { cone: false }).pipe(Effect.mapError(asSubmoduleError("git sparse-checkout set --no-cone", repoPath)));
|
|
144
116
|
sparseApplied.push(name);
|
|
145
117
|
}
|
|
146
118
|
}
|
|
@@ -157,27 +129,22 @@ const ReposManagerLive = Layer.effect(ReposManager, Effect.gen(function* () {
|
|
|
157
129
|
return last.endsWith(".git") ? last.slice(0, -4) : last;
|
|
158
130
|
};
|
|
159
131
|
/**
|
|
160
|
-
* Fetch `ref` shallow into a submodule
|
|
161
|
-
* `fetch origin tag <ref>`
|
|
162
|
-
* `fetch origin <ref
|
|
132
|
+
* Fetch `ref` shallow into a submodule via the kit's `Git.fetchAny`:
|
|
133
|
+
* tag-form fetch first (tags need the explicit `fetch origin tag <ref>`
|
|
134
|
+
* form), falling back to a plain `fetch origin <ref>` on
|
|
135
|
+
* `UnknownRefError` OR `GitCommandError` (unclassified tag-form stderr
|
|
136
|
+
* keeps the v3 any-failure fallback). `NotARepositoryError` deliberately
|
|
137
|
+
* does NOT fall back — a plain fetch in a non-repo fails identically, so
|
|
138
|
+
* retrying only doubled the latency of a certain failure. When both
|
|
139
|
+
* attempts fail, the plain fetch's error surfaces.
|
|
163
140
|
*/
|
|
164
|
-
const fetchRef = (sub, ref) =>
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
"origin",
|
|
169
|
-
"tag",
|
|
170
|
-
ref
|
|
171
|
-
]).pipe(Effect.orElse(() => runGit(sub, [
|
|
172
|
-
"fetch",
|
|
173
|
-
"--depth",
|
|
174
|
-
"1",
|
|
175
|
-
"origin",
|
|
176
|
-
ref
|
|
177
|
-
])));
|
|
141
|
+
const fetchRef = (sub, ref) => git.fetchAny(sub, {
|
|
142
|
+
ref,
|
|
143
|
+
depth: 1
|
|
144
|
+
}).pipe(Effect.mapError(asSubmoduleError(`git fetch --depth 1 origin ${ref}`, sub)));
|
|
178
145
|
const add = (root, options) => Effect.gen(function* () {
|
|
179
146
|
const name = options.name ?? repoSlug(options.url);
|
|
180
|
-
yield* Schema.
|
|
147
|
+
yield* Schema.decodeUnknownEffect(RepoName)(name).pipe(Effect.mapError(() => new ReposConfigError({
|
|
181
148
|
path: MANIFEST_PATH,
|
|
182
149
|
reason: `invalid repo name "${name}": must be non-empty, contain no "/" or "\\", and not be "." or ".."`,
|
|
183
150
|
kind: "invalid"
|
|
@@ -190,33 +157,15 @@ const ReposManagerLive = Layer.effect(ReposManager, Effect.gen(function* () {
|
|
|
190
157
|
}));
|
|
191
158
|
const repoPath = `${REPOS_DIR}/${name}`;
|
|
192
159
|
const subPath = path.join(root, repoPath);
|
|
193
|
-
yield*
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
repoPath
|
|
200
|
-
]);
|
|
201
|
-
yield* runGit(root, [
|
|
202
|
-
"config",
|
|
203
|
-
"-f",
|
|
204
|
-
".gitmodules",
|
|
205
|
-
`submodule.${repoPath}.shallow`,
|
|
206
|
-
"true"
|
|
207
|
-
]);
|
|
160
|
+
yield* git.submoduleAdd(root, {
|
|
161
|
+
url: options.url,
|
|
162
|
+
path: repoPath,
|
|
163
|
+
depth: 1
|
|
164
|
+
}).pipe(Effect.mapError(asSubmoduleError(`git submodule add --depth 1 ${options.url} ${repoPath}`, root)));
|
|
165
|
+
yield* git.configSet(root, `submodule.${repoPath}.shallow`, "true", { file: ".gitmodules" }).pipe(Effect.mapError(asSubmoduleError(`git config -f .gitmodules submodule.${repoPath}.shallow true`, root)));
|
|
208
166
|
yield* fetchRef(subPath, options.ref);
|
|
209
|
-
yield*
|
|
210
|
-
|
|
211
|
-
"--detach",
|
|
212
|
-
"FETCH_HEAD"
|
|
213
|
-
]);
|
|
214
|
-
if (options.sparse && options.sparse.length > 0) yield* runGit(subPath, [
|
|
215
|
-
"sparse-checkout",
|
|
216
|
-
"set",
|
|
217
|
-
"--no-cone",
|
|
218
|
-
...options.sparse
|
|
219
|
-
]);
|
|
167
|
+
yield* git.checkout(subPath, "FETCH_HEAD", { detach: true }).pipe(Effect.mapError(asSubmoduleError("git checkout --detach FETCH_HEAD", subPath)));
|
|
168
|
+
if (options.sparse && options.sparse.length > 0) yield* git.sparseCheckoutSet(subPath, options.sparse, { cone: false }).pipe(Effect.mapError(asSubmoduleError("git sparse-checkout set --no-cone", subPath)));
|
|
220
169
|
const entry = {
|
|
221
170
|
url: options.url,
|
|
222
171
|
ref: options.ref,
|
|
@@ -227,12 +176,11 @@ const ReposManagerLive = Layer.effect(ReposManager, Effect.gen(function* () {
|
|
|
227
176
|
...manifest.repos,
|
|
228
177
|
[name]: entry
|
|
229
178
|
} });
|
|
230
|
-
yield*
|
|
231
|
-
"add",
|
|
179
|
+
yield* git.add(root, [
|
|
232
180
|
".gitmodules",
|
|
233
181
|
MANIFEST_PATH,
|
|
234
182
|
repoPath
|
|
235
|
-
]);
|
|
183
|
+
]).pipe(Effect.mapError(asSubmoduleError(`git add .gitmodules ${MANIFEST_PATH} ${repoPath}`, root)));
|
|
236
184
|
return {
|
|
237
185
|
name,
|
|
238
186
|
ref: options.ref,
|
|
@@ -245,14 +193,10 @@ const ReposManagerLive = Layer.effect(ReposManager, Effect.gen(function* () {
|
|
|
245
193
|
if (!entry) return yield* Effect.fail(new RepoNotFoundError({ name }));
|
|
246
194
|
const repoPath = `${REPOS_DIR}/${name}`;
|
|
247
195
|
const subPath = path.join(root, repoPath);
|
|
248
|
-
const oldCommit = yield*
|
|
196
|
+
const oldCommit = yield* git.revParse(subPath, "HEAD").pipe(Effect.orElseSucceed(() => null));
|
|
249
197
|
yield* fetchRef(subPath, ref);
|
|
250
|
-
yield*
|
|
251
|
-
|
|
252
|
-
"--detach",
|
|
253
|
-
"FETCH_HEAD"
|
|
254
|
-
]);
|
|
255
|
-
const newCommit = yield* runGit(subPath, ["rev-parse", "HEAD"]);
|
|
198
|
+
yield* git.checkout(subPath, "FETCH_HEAD", { detach: true }).pipe(Effect.mapError(asSubmoduleError("git checkout --detach FETCH_HEAD", subPath)));
|
|
199
|
+
const newCommit = yield* git.revParse(subPath, "HEAD").pipe(Effect.mapError(asSubmoduleError("git rev-parse HEAD", subPath)));
|
|
256
200
|
yield* configStore.write(root, { repos: {
|
|
257
201
|
...manifest.repos,
|
|
258
202
|
[name]: {
|
|
@@ -260,11 +204,7 @@ const ReposManagerLive = Layer.effect(ReposManager, Effect.gen(function* () {
|
|
|
260
204
|
ref
|
|
261
205
|
}
|
|
262
206
|
} });
|
|
263
|
-
yield*
|
|
264
|
-
"add",
|
|
265
|
-
MANIFEST_PATH,
|
|
266
|
-
repoPath
|
|
267
|
-
]);
|
|
207
|
+
yield* git.add(root, [MANIFEST_PATH, repoPath]).pipe(Effect.mapError(asSubmoduleError(`git add ${MANIFEST_PATH} ${repoPath}`, root)));
|
|
268
208
|
const staleNoteIds = (entry.notes ?? []).filter((note) => note.ref !== ref).map((note) => note.id);
|
|
269
209
|
return {
|
|
270
210
|
name,
|
|
@@ -371,14 +311,14 @@ const ReposManagerLive = Layer.effect(ReposManager, Effect.gen(function* () {
|
|
|
371
311
|
noteCount: updatedNotes.length
|
|
372
312
|
};
|
|
373
313
|
});
|
|
374
|
-
return
|
|
314
|
+
return {
|
|
375
315
|
status,
|
|
376
316
|
sync,
|
|
377
317
|
add,
|
|
378
318
|
pin,
|
|
379
319
|
note
|
|
380
|
-
}
|
|
320
|
+
};
|
|
381
321
|
}));
|
|
382
322
|
|
|
383
323
|
//#endregion
|
|
384
|
-
export { ReposManager,
|
|
324
|
+
export { ReposManager, ReposManagerLive, STALE_LOCK_MAX_AGE_MS };
|
package/schemas/CommentStyle.js
CHANGED