@savvy-web/cli 2.2.1 → 2.3.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 CHANGED
@@ -55,6 +55,21 @@ npx savvy clean --globs dist,.turbo,coverage
55
55
  - `savvy commit` — the husky/Claude hook handlers (session-start, pre-commit-message, post-commit-verify).
56
56
  - `savvy changeset` — changeset lint, check, transform, version, config validation, and dependency changesets.
57
57
  - `savvy lint` — formatters for package.json, the pnpm workspace file and YAML.
58
+ - `savvy repos` — the vendored reference repos declared in `.repos/config.json`: `status`, `sync`, `pin`, `add` and `note`.
59
+
60
+ ## Vendored repos are read-only
61
+
62
+ `savvy repos sync`, `add` and `pin` leave every vendored tree under `.repos/` read-only at the OS level — files `0444`, directories `0555`. Reading a vendored source needs no extra step; writing to one fails with `EACCES` by design, because those trees mirror an upstream repo at a pinned ref and any local edit is lost on the next sync.
63
+
64
+ ```bash
65
+ npx savvy repos sync
66
+ # reconciles .repos/ with the manifest and re-locks every tree
67
+
68
+ npx savvy repos pin effect v4.0.0
69
+ # fetches, checks out the new ref, re-locks, and stages the gitlink and manifest
70
+ ```
71
+
72
+ A manual `chmod` only holds until the next `sync` or `pin`. Route changes through `savvy repos` instead.
58
73
 
59
74
  Run any command with `--help` to see its full surface:
60
75
 
package/cli/index.js CHANGED
@@ -76,7 +76,7 @@ const rootCommand = Command.make("savvy").pipe(Command.withSubcommands([
76
76
  * CLI application: reads argv from the Stdio service provided by NodeServices.
77
77
  * (v4's `Command.run` takes only `version` — the name comes from the root command.)
78
78
  */
79
- const cli = Command.run(rootCommand, { version: "2.2.1" });
79
+ const cli = Command.run(rootCommand, { version: "2.3.0" });
80
80
  /**
81
81
  * Shared workspace services from `@effected/workspaces`, wired as a
82
82
  * self-contained unit and built ONCE (layers memoize by reference).
@@ -111,11 +111,11 @@ const BaseLive = Layer.mergeAll(WorkspaceLive, GitLive, ChangesetConfigReader.la
111
111
  */
112
112
  const InspectorAndAnalyzerLive = Changesets.BranchAnalyzer.layer.pipe(Layer.provideMerge(Changesets.ReleasePlanner.layer), Layer.provideMerge(Changesets.ConfigInspector.layer));
113
113
  /**
114
- * `Repos.ReposManager`, provided its `Repos.ReposConfigStore.layer` dependency
115
- * and the shared `Git` service. Platform requirements (`FileSystem`, `Path`)
116
- * flow up to `NodeServices.layer`.
114
+ * `Repos.ReposManager`, provided its `Repos.ReposConfigStore.layer` and
115
+ * `Repos.ReposLockdown.layer` dependencies plus the shared `Git` service.
116
+ * Platform requirements (`FileSystem`, `Path`) flow up to `NodeServices.layer`.
117
117
  */
118
- const ReposGroupLive = Repos.ReposManager.layer.pipe(Layer.provide(Repos.ReposConfigStore.layer), Layer.provide(GitLive));
118
+ const ReposGroupLive = Repos.ReposManager.layer.pipe(Layer.provide(Repos.ReposConfigStore.layer), Layer.provide(Repos.ReposLockdown.layer), Layer.provide(GitLive));
119
119
  /**
120
120
  * `ToolDiscovery` from `@effected/commands`, wired to this workspace. Its
121
121
  * `LocalExec` contract — the argv prefix that runs a project-local binary — is
@@ -15,9 +15,10 @@ import { Argument, Command, Flag } from "effect/unstable/cli";
15
15
  * is the common, friendly case and always exits 0 (the manifest is created
16
16
  * on demand by `add` itself, so this only fires on a read that raced a
17
17
  * concurrent removal). A `ReposConfigError` with kind `"invalid"` means the
18
- * manifest exists but is corrupt or unreadable, and `GitSubmoduleError`
19
- * means the underlying git command failed -- both are real failures, logged
20
- * and reported via a non-zero exit code.
18
+ * manifest exists but is corrupt or unreadable, `GitSubmoduleError` means
19
+ * the underlying git command failed, and `ReposLockdownError` means the
20
+ * OS-permission lockdown pass on a vendored tree failed -- all three are
21
+ * real failures, logged and reported via a non-zero exit code.
21
22
  *
22
23
  * @example
23
24
  * ```bash
@@ -51,6 +52,9 @@ const runReposAdd = (cwd, opts) => Effect.gen(function* () {
51
52
  }), Effect.catchTag("GitSubmoduleError", (error) => {
52
53
  process.exitCode = 1;
53
54
  return Effect.log(error.message);
55
+ }), Effect.catchTag("ReposLockdownError", (error) => {
56
+ process.exitCode = 1;
57
+ return Effect.log(error.message);
54
58
  }));
55
59
  /* v8 ignore start -- CLI registration; handler tested via runReposAdd */
56
60
  const addCommand = Command.make("add", {
@@ -14,9 +14,11 @@ import { Argument, Command, Flag } from "effect/unstable/cli";
14
14
  * A `ReposConfigError` with kind `"missing"` -- nothing vendored yet -- is
15
15
  * the common, friendly case and always exits 0. A `ReposConfigError` with
16
16
  * kind `"invalid"` means the manifest exists but is corrupt or unreadable,
17
- * `GitSubmoduleError` means the underlying git command failed, and
18
- * `RepoNotFoundError` means the named repo isn't in the manifest -- all
19
- * three are real failures, logged and reported via a non-zero exit code.
17
+ * `GitSubmoduleError` means the underlying git command failed,
18
+ * `RepoNotFoundError` means the named repo isn't in the manifest, and
19
+ * `ReposLockdownError` means the OS-permission lockdown pass on a vendored
20
+ * tree failed -- all four are real failures, logged and reported via a
21
+ * non-zero exit code.
20
22
  *
21
23
  * @example
22
24
  * ```bash
@@ -51,6 +53,9 @@ const runReposPin = (cwd, name, ref) => Effect.gen(function* () {
51
53
  }), Effect.catchTag("RepoNotFoundError", (error) => {
52
54
  process.exitCode = 1;
53
55
  return Effect.log(error.message);
56
+ }), Effect.catchTag("ReposLockdownError", (error) => {
57
+ process.exitCode = 1;
58
+ return Effect.log(error.message);
54
59
  }));
55
60
  /* v8 ignore start -- CLI registration; handler tested via runReposPin */
56
61
  const pinCommand = Command.make("pin", {
@@ -12,9 +12,11 @@ import { Command, Flag } from "effect/unstable/cli";
12
12
  * locks left behind by an interrupted fetch. Sync is idempotent repair, so
13
13
  * a `ReposConfigError` with kind `"missing"` -- the common, friendly case
14
14
  * (nothing to sync yet) -- always exits 0. A `ReposConfigError` with kind
15
- * `"invalid"` means the manifest exists but is corrupt or unreadable, and
16
- * `GitSubmoduleError` means the underlying git command failed -- both are
17
- * real failures, logged and reported via a non-zero exit code.
15
+ * `"invalid"` means the manifest exists but is corrupt or unreadable,
16
+ * `GitSubmoduleError` means the underlying git command failed, and
17
+ * `ReposLockdownError` means the OS-permission lockdown pass on a vendored
18
+ * tree failed -- all three are real failures, logged and reported via a
19
+ * non-zero exit code.
18
20
  *
19
21
  * @example
20
22
  * ```bash
@@ -44,6 +46,9 @@ const runReposSync = (cwd) => Effect.gen(function* () {
44
46
  }), Effect.catchTag("GitSubmoduleError", (error) => {
45
47
  process.exitCode = 1;
46
48
  return Effect.log(error.message);
49
+ }), Effect.catchTag("ReposLockdownError", (error) => {
50
+ process.exitCode = 1;
51
+ return Effect.log(error.message);
47
52
  }));
48
53
  /* v8 ignore start -- CLI registration; handler tested via runReposSync */
49
54
  const syncCommand = Command.make("sync", { cwd: cwdOption }, ({ cwd }) => runReposSync(cwd)).pipe(Command.withDescription("Reconcile vendored submodules with the manifest: init missing, apply sparse, clear locks"));
@@ -28,6 +28,10 @@ const _reposCommand = Command.make("repos").pipe(Command.withSubcommands([
28
28
  * The requirements channel names `Repos.ReposManager` rather than `never`:
29
29
  * `Command.withSubcommands` propagates each subcommand's requirements up into
30
30
  * the group's `R`, which the root assembly discharges via `AppLive`.
31
+ *
32
+ * The error channel now also names `Repos.ReposLockdownError`: `sync`/`add`/
33
+ * `pin` unlock and re-lock the vendored tree around their git mutations
34
+ * (Task 2), so a lockdown chmod failure can surface from any of the three.
31
35
  */
32
36
  const reposCommand = _reposCommand;
33
37
 
package/index.d.ts CHANGED
@@ -297,8 +297,12 @@ declare const runReposSync: (cwd: string) => Effect.Effect<void, never, Repos.Re
297
297
  * The requirements channel names `Repos.ReposManager` rather than `never`:
298
298
  * `Command.withSubcommands` propagates each subcommand's requirements up into
299
299
  * the group's `R`, which the root assembly discharges via `AppLive`.
300
+ *
301
+ * The error channel now also names `Repos.ReposLockdownError`: `sync`/`add`/
302
+ * `pin` unlock and re-lock the vendored tree around their git mutations
303
+ * (Task 2), so a lockdown chmod failure can surface from any of the three.
300
304
  */
301
- declare const reposCommand: Command.Command<"repos", Record<string, never>, Record<string, never>, Repos.GitSubmoduleError, Repos.ReposManager>;
305
+ declare const reposCommand: Command.Command<"repos", Record<string, never>, Record<string, never>, Repos.GitSubmoduleError | Repos.ReposLockdownError, Repos.ReposManager>;
302
306
  //#endregion
303
307
  export { changesetCommand, checkCommand, commitCommand, initCommand, lintCommand, reposCommand, runChangesetCheck, runChangesetInit, runCheck, runCli, runCommitCheck, runCommitInit, runInit, runLintCheck, runLintInit, runReposStatus, runReposSync };
304
308
  //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/cli",
3
- "version": "2.2.1",
3
+ "version": "2.3.0",
4
4
  "private": false,
5
5
  "description": "The savvy CLI — unified commit, changeset, and lint commands for the Silk Suite",
6
6
  "homepage": "https://github.com/savvy-web/systems/tree/main/packages/cli",
@@ -32,13 +32,13 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "@effect/platform-node": "4.0.0-beta.101",
35
- "@effected/commands": "^0.2.1",
35
+ "@effected/commands": "^0.3.1",
36
36
  "@effected/git": "^0.5.2",
37
37
  "@effected/jsonc": "^0.5.2",
38
38
  "@effected/templates": "^0.1.1",
39
- "@effected/workspaces": "^0.9.5",
39
+ "@effected/workspaces": "^0.10.0",
40
40
  "@effected/yaml": "^0.6.1",
41
- "@savvy-web/silk-effects": "5.3.1",
41
+ "@savvy-web/silk-effects": "5.4.0",
42
42
  "effect": "4.0.0-beta.101"
43
43
  }
44
44
  }