@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 +15 -0
- package/cli/index.js +5 -5
- package/commands/repos/commands/add.js +7 -3
- package/commands/repos/commands/pin.js +8 -3
- package/commands/repos/commands/sync.js +8 -3
- package/commands/repos/index.js +4 -0
- package/index.d.ts +5 -1
- package/package.json +4 -4
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.
|
|
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`
|
|
115
|
-
*
|
|
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,
|
|
19
|
-
*
|
|
20
|
-
*
|
|
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,
|
|
18
|
-
* `RepoNotFoundError` means the named repo isn't in the manifest
|
|
19
|
-
*
|
|
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,
|
|
16
|
-
* `GitSubmoduleError` means the underlying git command failed
|
|
17
|
-
*
|
|
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"));
|
package/commands/repos/index.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
39
|
+
"@effected/workspaces": "^0.10.0",
|
|
40
40
|
"@effected/yaml": "^0.6.1",
|
|
41
|
-
"@savvy-web/silk-effects": "5.
|
|
41
|
+
"@savvy-web/silk-effects": "5.4.0",
|
|
42
42
|
"effect": "4.0.0-beta.101"
|
|
43
43
|
}
|
|
44
44
|
}
|