@6reduk/workspace-pipeline 0.6.0 → 0.7.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 +14 -1
- package/docs/desired-state.md +243 -0
- package/docs/lifecycle-cli.md +11 -6
- package/docs/migrations/unity.md +1 -1
- package/docs/rebind.md +1 -1
- package/docs/repositories.md +2 -2
- package/package.json +1 -1
- package/schemas/pipeline-v2.schema.json +43 -0
- package/src/cli.js +5 -2
- package/src/commands/desired-lifecycle.js +93 -0
- package/src/commands/desired-recover.js +28 -0
- package/src/commands/desired-remove.js +48 -0
- package/src/commands/desired-setup.js +47 -0
- package/src/commands/dispatch.js +27 -16
- package/src/commands/init.js +1 -1
- package/src/commands/migration.js +2 -3
- package/src/commands/output.js +10 -0
- package/src/contracts/desired-state.js +56 -0
- package/src/desired-state/apply-files.js +172 -0
- package/src/desired-state/binding.js +23 -0
- package/src/desired-state/doctor.js +106 -0
- package/src/desired-state/global-settings.js +74 -0
- package/src/desired-state/inventory.js +102 -0
- package/src/desired-state/legacy-state.js +37 -0
- package/src/desired-state/local-settings.js +40 -0
- package/src/desired-state/lock-recovery.js +78 -0
- package/src/desired-state/records.js +75 -0
- package/src/desired-state/removal.js +32 -0
- package/src/desired-state/reset.js +27 -0
- package/src/desired-state/retirement.js +37 -0
- package/src/desired-state/settings.js +125 -0
- package/src/desired-state/source.js +41 -0
- package/src/operations/lock.js +3 -2
- package/src/source/desired-package.js +33 -0
- package/src/source/git.js +7 -3
package/README.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Workspace Pipeline CLI
|
|
2
2
|
|
|
3
|
+
## Desired-state installation (0.7.0 release candidate)
|
|
4
|
+
|
|
5
|
+
The working tree adds manifest schema 2: ordinary `setup --source ... --adapters ...`
|
|
6
|
+
creates `workspace.json` after confirmation; `update` uses it without a separate
|
|
7
|
+
preview file. Whole-owned adapter contents are replaced, including custom files;
|
|
8
|
+
backups are opt-in with `--backup`. Shared configurations change only declared fields.
|
|
9
|
+
See [setup, update, doctor and source authoring](docs/desired-state.md).
|
|
10
|
+
This is not in the published 0.6.0 package. The sections below describe the legacy
|
|
11
|
+
release and explicit recovery routes; do not mix their backup/preview contracts
|
|
12
|
+
with schema 2.
|
|
13
|
+
|
|
14
|
+
## Published legacy lifecycle
|
|
15
|
+
|
|
3
16
|
Everyday update: `workspace-pipeline update --workspace <directory>` shows the
|
|
4
17
|
changes and asks for confirmation. Use `--yes` for unattended application or
|
|
5
18
|
`--preview --json` to retain the advanced saved-plan workflow. See
|
|
@@ -77,7 +90,7 @@ Startup cleanup is disabled unless the user enables a local policy via
|
|
|
77
90
|
that policy; public setup/update uses the same coordinator. Policy commands only
|
|
78
91
|
write `.pipeline/retention.json`, never harness settings or project documents.
|
|
79
92
|
Repository init/adopt previews acquire the explicitly selected Git pipeline source;
|
|
80
|
-
remote
|
|
93
|
+
remote acquisition follows the selected source without an extra flag. No command launches MCP or changes
|
|
81
94
|
credentials. Repository setup and provider activation are separate steps.
|
|
82
95
|
See [repository commands](docs/repositories.md), [recovery](docs/repository-recovery.md)
|
|
83
96
|
and [manual recovery boundaries](docs/repository-manual-recovery.md).
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# Install and update a workspace pipeline
|
|
2
|
+
|
|
3
|
+
Development status: these commands require the unreleased 0.7.0 desired-state build,
|
|
4
|
+
not the published 0.6.0 package. `workspace-pipeline` below means that build's
|
|
5
|
+
installed executable. During development use `node <cli-repository>/src/cli.js`.
|
|
6
|
+
Do not run these examples against a real workspace until the migration is reviewed.
|
|
7
|
+
|
|
8
|
+
## What is installed
|
|
9
|
+
|
|
10
|
+
A Git repository contains a schema-2 `pipeline.json` (or YAML equivalent). It
|
|
11
|
+
declares which files to deliver and which named configuration fields to set or
|
|
12
|
+
remove. The CLI does not run scripts from the pipeline source.
|
|
13
|
+
|
|
14
|
+
The wrapper is an existing directory containing your project repository or
|
|
15
|
+
repositories. The CLI configures the wrapper, not the game/application code.
|
|
16
|
+
It does not move, clone, initialize or delete project repositories in this route.
|
|
17
|
+
Create the directory first and place your repositories where you intend them to live.
|
|
18
|
+
|
|
19
|
+
## First installation — no prepared JSON file required
|
|
20
|
+
|
|
21
|
+
Example with a committed local pipeline source:
|
|
22
|
+
|
|
23
|
+
```powershell
|
|
24
|
+
workspace-pipeline setup --workspace "C:\Work\Game" --source "C:\Sources\my-pipeline" --subdirectory pipelines/unity --adapters codex,claude-grok
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Use actual adapter IDs declared by your source. Unity currently supplies `codex`
|
|
28
|
+
and `claude-grok`; the latter delivers Claude files for both harnesses. Kimi is
|
|
29
|
+
deferred. Native plugins are not installed by this command.
|
|
30
|
+
|
|
31
|
+
`--source` also accepts an HTTPS or SSH Git URL. Git authentication remains yours;
|
|
32
|
+
never place tokens or passwords in the URL. A relative local source path is resolved
|
|
33
|
+
from the shell's current directory, then stored relative to the wrapper. A local
|
|
34
|
+
source on another Windows volume is not supported by the current source schema.
|
|
35
|
+
Only committed Git contents are used, not uncommitted edits. `--ref` defaults to
|
|
36
|
+
`HEAD`, and `--subdirectory` to `.`. Fetching the selected remote source is part of
|
|
37
|
+
preparation; no separate network authorization flag is required. Setup, update and reset may fetch the configured remote Git source
|
|
38
|
+
before confirmation of workspace changes.
|
|
39
|
+
|
|
40
|
+
By default the layout is one repository named `game` at `project`, with documentation
|
|
41
|
+
at `project/docs`. The displayed summary shows these choices before confirmation.
|
|
42
|
+
Override them when the actual structure differs:
|
|
43
|
+
|
|
44
|
+
```powershell
|
|
45
|
+
workspace-pipeline setup --workspace "C:\Work\Game" --source "C:\Sources\my-pipeline" --adapters codex --repository game=game-repo --documentation game=docs
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
For multiple repositories, repeat `--repository` and explicitly bind documentation:
|
|
49
|
+
|
|
50
|
+
```powershell
|
|
51
|
+
workspace-pipeline setup --workspace "C:\Work\Platform" --source "C:\Sources\platform-pipeline" --adapters codex --repository api=services/api --repository docs=knowledge --documentation docs=.
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Repository and documentation paths are relative and use `/` for separators. IDs
|
|
55
|
+
are lookup keys, not inferred project types. Repository paths must not overlap.
|
|
56
|
+
The CLI saves the resulting declaration as `workspace.json` only after confirmation.
|
|
57
|
+
It is a source/layout configuration, not an execution preview. Existing
|
|
58
|
+
`workspace.json` is not overwritten by `setup --source`; use the installed binding
|
|
59
|
+
with ordinary `setup`/`update`, or explicitly edit the declaration for a supported
|
|
60
|
+
change. Pipeline-ID switching is not implemented in the new engine.
|
|
61
|
+
|
|
62
|
+
## Update and customization loss
|
|
63
|
+
|
|
64
|
+
```powershell
|
|
65
|
+
workspace-pipeline doctor --workspace "C:\Work\Game"
|
|
66
|
+
workspace-pipeline update --workspace "C:\Work\Game"
|
|
67
|
+
workspace-pipeline update --workspace "C:\Work\Game" --backup
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`update` resolves the Git ref saved in `workspace.json`, shows affected paths and
|
|
71
|
+
configuration changes, and asks for confirmation. Enter or an answer other than
|
|
72
|
+
`y`, `yes`, `д`, `да` cancels. `--yes` authorizes unattended application; noninteractive
|
|
73
|
+
use otherwise requires `--preview`. `--json` changes presentation, not authorization.
|
|
74
|
+
|
|
75
|
+
There is **no backup by default**. A whole-owned directory is replaced with the
|
|
76
|
+
declared contents: extra files inside it are removed and modified delivered files
|
|
77
|
+
are overwritten. Owned single files are replaced individually. Other directories
|
|
78
|
+
are not implicitly owned. Only declared fields of shared JSON/TOML configs change;
|
|
79
|
+
credentials, models and unrelated fields are preserved. Obsolete recorded targets
|
|
80
|
+
are retired on update. Invalid paths, links, malformed settings and unavailable
|
|
81
|
+
sources are errors, not customization conflicts to bypass.
|
|
82
|
+
|
|
83
|
+
`--backup` saves affected old adapter files/local configuration under
|
|
84
|
+
`.pipeline/backups/desired-*`. A requested global backup stays inside the relevant
|
|
85
|
+
user profile. Paths are reported. These backups may contain secrets: keep them local.
|
|
86
|
+
If requested backup fails, replacement does not proceed.
|
|
87
|
+
|
|
88
|
+
Global changes are explicitly displayed with their resolved user path and affect
|
|
89
|
+
other workspaces. Unity's Claude/Grok adapter sets five user-wide Grok Claude
|
|
90
|
+
compatibility flags; it does not change account/model credentials or grant trust.
|
|
91
|
+
|
|
92
|
+
## Doctor, preview and interrupted work
|
|
93
|
+
|
|
94
|
+
`doctor` lists extra, modified and missing delivered files, unsafe paths and differing
|
|
95
|
+
configuration fields. It displays the recorded Git source, revision and digest.
|
|
96
|
+
For Git-bound installations it also reports a missing, invalid or changed
|
|
97
|
+
`workspace.json` (source, layout and recorded adapter selection). Repository roots
|
|
98
|
+
from the installation and a valid current declaration remain protected. Changing
|
|
99
|
+
the declaration is reported as drift, not silently accepted as an installed state.
|
|
100
|
+
Older records without adapter IDs cannot verify selection equality; records without
|
|
101
|
+
Git binding retain file/config-only inspection.
|
|
102
|
+
It does not fetch updates or certify source trust, native skill discovery, MCP or
|
|
103
|
+
runtime execution. Version labels do not require rewriting project documentation.
|
|
104
|
+
|
|
105
|
+
`setup ... --preview --json` and `update ... --preview --json` inspect without applying
|
|
106
|
+
or creating the descriptor. In schema 2 this is an observation, **not** a saved-plan
|
|
107
|
+
authorization accepted by `--apply`. The old `--apply --preview <file>` route is
|
|
108
|
+
retained for schema-1/legacy operations only; a schema-2 replayable-plan route is
|
|
109
|
+
not implemented. Normal schema-2 use needs neither external file nor `--apply`.
|
|
110
|
+
|
|
111
|
+
Replacement is not atomic across all files and has no automatic rollback. After a
|
|
112
|
+
failure, inspect the reported partial state and `doctor`. The pending record binds
|
|
113
|
+
the same Git revision/digest; retry must use that same source and selection. If a
|
|
114
|
+
branch moved, the new revision is refused as a retry; `reset` can reacquire the
|
|
115
|
+
recorded commit instead (see below). Do not delete pending records
|
|
116
|
+
or locks to force success. For an abandoned lock use the bounded recovery command
|
|
117
|
+
below; old legacy recovery commands do not apply to these records.
|
|
118
|
+
The schema-2 switch command route is not yet implemented;
|
|
119
|
+
legacy commands are not a substitute. These are release limitations, not a claim
|
|
120
|
+
that the entire migration is finished.
|
|
121
|
+
|
|
122
|
+
## Reset to the installed version
|
|
123
|
+
|
|
124
|
+
```powershell
|
|
125
|
+
workspace-pipeline reset --workspace "C:\Work\Game"
|
|
126
|
+
workspace-pipeline reset --workspace "C:\Work\Game" --backup
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The default is the recorded installed Git commit, **not latest**. Reset reacquires
|
|
130
|
+
that commit from the saved source and checks its recorded digest before changing
|
|
131
|
+
anything. It restores delivered files and named settings and removes extra files
|
|
132
|
+
inside owned directories. It uses the same confirmation and optional backup rules
|
|
133
|
+
as update. `--preview --json` only displays the proposed effect.
|
|
134
|
+
|
|
135
|
+
Use `update` to move to the current source ref. Reset leaves `workspace.json`
|
|
136
|
+
unchanged, so later updates still follow that ref. If the declaration's source,
|
|
137
|
+
adapter selection or layout changed, reset refuses rather than guessing which
|
|
138
|
+
mapping to restore. Missing binding/adapter metadata or unavailable historical Git
|
|
139
|
+
objects likewise prevents reset before replacement. This is not an offline backup
|
|
140
|
+
restore. No project files are reset and no Git checkout/reset is performed.
|
|
141
|
+
|
|
142
|
+
After a handled installation interruption, reset restores the pending installation's
|
|
143
|
+
recorded commit even if its branch moved. It does not cancel an unfinished removal,
|
|
144
|
+
erase pending metadata or release a stale lock. Global named settings can be
|
|
145
|
+
restored just as during update, with the shared effect displayed before consent.
|
|
146
|
+
|
|
147
|
+
## Recover an abandoned lock after process termination
|
|
148
|
+
|
|
149
|
+
```powershell
|
|
150
|
+
workspace-pipeline recover-lock --workspace "C:\Work\Game"
|
|
151
|
+
# Only when the reported global Grok lock belongs to this workspace:
|
|
152
|
+
workspace-pipeline recover-lock --workspace "C:\Work\Game" --global
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
This command does not kill processes, resume installation or modify configuration.
|
|
156
|
+
It verifies the lock's purpose/workspace, checks that its PID is absent on the same
|
|
157
|
+
host, displays the target and asks for confirmation (`--yes` for unattended use).
|
|
158
|
+
`--preview --json` is read-only. A live/reused PID, unknown host, malformed or older
|
|
159
|
+
owner format, missing owner, links, foreign entries or a conflicting repository
|
|
160
|
+
operation prevent recovery. Lock age alone is never sufficient.
|
|
161
|
+
|
|
162
|
+
The exact stale directory is renamed to a temporary sibling, its owner identity
|
|
163
|
+
is rechecked, and its owner file and directory are deleted nonrecursively. No
|
|
164
|
+
lock archive is retained after success. Pending operation records remain intact.
|
|
165
|
+
Run doctor and explicitly retry reset (installation) or remove (removal) afterward.
|
|
166
|
+
No automatic retry is performed.
|
|
167
|
+
|
|
168
|
+
Recoverers are serialized using the existing OS lease (Windows/Linux only).
|
|
169
|
+
After the atomic rename, recovery never touches the original lock path again:
|
|
170
|
+
a new writer may already own it. If deletion fails, the error reports
|
|
171
|
+
`lockReleased: true` and `residualPath` for inspection. A process crash after
|
|
172
|
+
rename can leave a `*-retiring-<uuid>` sibling; it is not an active lock, and
|
|
173
|
+
recovery does not automatically sweep such leftovers or older recovered-lock
|
|
174
|
+
archives. No recursive cleanup or archive-retention command is provided.
|
|
175
|
+
|
|
176
|
+
These are cooperative-process safeguards, not isolation against hostile concurrent
|
|
177
|
+
filesystem mutation. Empty/partially written owners and old token-only global locks
|
|
178
|
+
need separate inspection, not a force-unlock flag. Source/package corruption and
|
|
179
|
+
partially written configuration are not repaired by lock recovery.
|
|
180
|
+
|
|
181
|
+
## Remove and reinstall
|
|
182
|
+
|
|
183
|
+
```powershell
|
|
184
|
+
workspace-pipeline remove --workspace "C:\Work\Game"
|
|
185
|
+
workspace-pipeline update --workspace "C:\Work\Game"
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
`remove` operates offline on the recorded installation, displays its scope and asks
|
|
189
|
+
for confirmation (`--yes` for unattended use). It removes the entire delivery,
|
|
190
|
+
including custom files in owned directories, and its named local configuration
|
|
191
|
+
fields. It does not need the Git source. `--backup` is optional, off by default.
|
|
192
|
+
`--preview --json` observes the removal without applying it.
|
|
193
|
+
|
|
194
|
+
Project repositories, unrelated config fields, `workspace.json`, existing journals
|
|
195
|
+
and backups are preserved. Shared user-wide Grok compatibility is deliberately
|
|
196
|
+
left unchanged because other workspaces may use it. Remove does not uninstall
|
|
197
|
+
native plugins or erase unknown/unrecorded old adapters. It does not support partial
|
|
198
|
+
provider selection. Missing/invalid ownership, unsafe paths and malformed shared
|
|
199
|
+
configuration cause refusal, not an unrestricted directory wipe.
|
|
200
|
+
|
|
201
|
+
Doctor then reports `not-installed`. Ordinary `update` or `setup` reinstalls using
|
|
202
|
+
the retained source declaration. A handled interruption during removal can be
|
|
203
|
+
continued with the same `remove` command; installation/update cannot replace its
|
|
204
|
+
unfinished marker. Conversely, finish an interrupted installation before removing
|
|
205
|
+
it. After a hard kill, recover abandoned locks first using the separate command.
|
|
206
|
+
|
|
207
|
+
## For pipeline authors
|
|
208
|
+
|
|
209
|
+
```json
|
|
210
|
+
{
|
|
211
|
+
"schemaVersion": 2,
|
|
212
|
+
"id": "example",
|
|
213
|
+
"version": "1.0.0",
|
|
214
|
+
"files": [{"source": "core", "target": ".example", "kind": "directory"}],
|
|
215
|
+
"adapters": {
|
|
216
|
+
"codex": {
|
|
217
|
+
"providers": ["codex"],
|
|
218
|
+
"files": [{"source": "instructions.md", "target": "AGENTS.md", "kind": "file"}],
|
|
219
|
+
"settings": []
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Top-level files are shared and installed once. Adapter files/settings are selected
|
|
226
|
+
by adapter ID; a provider cannot occur twice in a selection. Source directories
|
|
227
|
+
must contain committed files; Git does not supply empty directories. Targets cannot
|
|
228
|
+
overlap each other, repositories, CLI metadata, `workspace.json`, or whole shared
|
|
229
|
+
configuration files. Field declarations use `target`, JSON-pointer `pointer`,
|
|
230
|
+
`operation: set|remove` and a value only for `set`.
|
|
231
|
+
|
|
232
|
+
Supported configuration targets are `codex.workspace` (named agents/MCP),
|
|
233
|
+
`claude.workspace` (`enabledMcpjsonServers`), `claude.mcp` (named MCP),
|
|
234
|
+
`grok.workspace` (named MCP), and `grok.user` (five Claude compatibility booleans).
|
|
235
|
+
They map to CLI-defined paths; sources cannot provide arbitrary home filenames.
|
|
236
|
+
Hooks, credentials, model settings and executable installer extensions are outside
|
|
237
|
+
this configuration contract. Review delivered MCP commands before trusting a source.
|
|
238
|
+
|
|
239
|
+
Keep canonical rules in shared files and provider discovery files small. Validate
|
|
240
|
+
relative links in the installed layout, not just in the source repository. Test
|
|
241
|
+
fresh install, update after custom changes, removed upstream targets, optional
|
|
242
|
+
backup, interruption, and doctor using disposable profiles and the packed CLI.
|
|
243
|
+
Package checks are not human approval or proof of model behavior.
|
package/docs/lifecycle-cli.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Lifecycle CLI routing — development scope
|
|
2
2
|
|
|
3
|
+
This page describes schema-1 legacy operations and their saved-plan/recovery
|
|
4
|
+
contract. For the new schema-2 desired-state setup/update (no mandatory preview,
|
|
5
|
+
no default backup), see [the desired-state guide](desired-state.md). Do not apply
|
|
6
|
+
the legacy conflict/backup/launcher rules below to the new installer.
|
|
7
|
+
|
|
3
8
|
The generic dispatcher routes `setup`, `update`, `repair`, `remove`, `switch` and `continue` through
|
|
4
9
|
the native operation APIs and their coordinators. The packaged entrypoint supplies
|
|
5
10
|
compiled Codex, Claude, Kimi and Grok adapters. S11 review and native Kimi/Grok
|
|
@@ -63,8 +68,8 @@ The temporary preview is not a replacement for durable recovery evidence.
|
|
|
63
68
|
Once a trusted registry is provided by the CLI assembly:
|
|
64
69
|
|
|
65
70
|
```text
|
|
66
|
-
workspace-pipeline setup --workspace <absolute-wrapper> [--manifest <absolute-file>]
|
|
67
|
-
workspace-pipeline update --workspace <absolute-wrapper> [--manifest <absolute-file>]
|
|
71
|
+
workspace-pipeline setup --workspace <absolute-wrapper> [--manifest <absolute-file>]
|
|
72
|
+
workspace-pipeline update --workspace <absolute-wrapper> [--manifest <absolute-file>]
|
|
68
73
|
workspace-pipeline setup --workspace <absolute-wrapper> --apply --preview <absolute-json-file>
|
|
69
74
|
workspace-pipeline update --workspace <absolute-wrapper> --apply --preview <absolute-json-file>
|
|
70
75
|
```
|
|
@@ -72,7 +77,7 @@ workspace-pipeline update --workspace <absolute-wrapper> --apply --preview <abso
|
|
|
72
77
|
Preview displays a human-readable summary by default. Add `--json` to return
|
|
73
78
|
the complete prepared JSON on stdout for saving and subsequent apply. It may acquire a Git
|
|
74
79
|
source into temporary storage outside the wrapper, but does not write provider
|
|
75
|
-
configuration.
|
|
80
|
+
configuration. Remote Git acquisition follows the selected source. When manifest
|
|
76
81
|
is omitted, setup uses the standard workspace manifest; update uses the recorded
|
|
77
82
|
manifest origin. Relocation does not silently rebind it. Use the explicit
|
|
78
83
|
[manifest rebind workflow](rebind.md) to change that origin.
|
|
@@ -80,7 +85,7 @@ manifest origin. Relocation does not silently rebind it. Use the explicit
|
|
|
80
85
|
Save prepared JSON privately, inspect its operations and then explicitly invoke
|
|
81
86
|
`--apply --preview`. It may contain configuration secrets and absolute local
|
|
82
87
|
paths; do not commit it or send it to shared logs. Apply does not accept
|
|
83
|
-
`--manifest
|
|
88
|
+
`--manifest`, acquire a fresh source, or substitute a new preview.
|
|
84
89
|
Missing/stale staged data is an error, not permission to download replacements.
|
|
85
90
|
|
|
86
91
|
Apply binds the verb and exact wrapper before lock acquisition, then invokes
|
|
@@ -147,7 +152,7 @@ Removal preserves other installed providers and shared files while needed; full
|
|
|
147
152
|
removal restores taken-over content and removes only owned scope. Missing/corrupt
|
|
148
153
|
snapshots, backups, unresolved history or conflicting user edits fail closed.
|
|
149
154
|
Neither route reads the original manifest or downloads from Git. `--manifest`
|
|
150
|
-
|
|
155
|
+
is rejected. `--providers` and `--bundles` are remove-preview-only;
|
|
151
156
|
omitting both previews all installed providers and bundles. Use actual installed
|
|
152
157
|
bundle IDs; selecting a bundle member via `--providers` fails with
|
|
153
158
|
`remove.bundle-required`. The saved subject fixes the actual selection, so apply
|
|
@@ -168,7 +173,7 @@ Interrupted-operation continuation has a separate exact approval boundary below.
|
|
|
168
173
|
With the same trusted built-in registry restriction:
|
|
169
174
|
|
|
170
175
|
```text
|
|
171
|
-
workspace-pipeline switch --workspace <absolute-wrapper> --manifest <absolute-incoming-manifest>
|
|
176
|
+
workspace-pipeline switch --workspace <absolute-wrapper> --manifest <absolute-incoming-manifest>
|
|
172
177
|
workspace-pipeline switch --workspace <absolute-wrapper> --apply --preview <absolute-json-file>
|
|
173
178
|
```
|
|
174
179
|
|
package/docs/migrations/unity.md
CHANGED
|
@@ -7,7 +7,7 @@ synthetic testing; independent review and real deployment approval remain pendin
|
|
|
7
7
|
|
|
8
8
|
`workspace-pipeline migration unity preview --workspace <absolute-wrapper> --manifest <absolute-manifest>`
|
|
9
9
|
prepares the complete proposal, staging committed Git source outside the wrapper.
|
|
10
|
-
|
|
10
|
+
Selecting a remote Git source permits acquisition during preparation. Keep the JSON private: it can
|
|
11
11
|
contain full config bytes. The command does not disable plugins or install files.
|
|
12
12
|
|
|
13
13
|
`workspace-pipeline migration unity inspect --workspace <absolute-wrapper> --recovery <relative-record> --phase <phase>`
|
package/docs/rebind.md
CHANGED
|
@@ -27,7 +27,7 @@ and `proposed.manifest`. The first command only reads local state/history and th
|
|
|
27
27
|
new manifest. It does not fetch Git, move files, or activate the new origin.
|
|
28
28
|
|
|
29
29
|
`--accept-rebind` explicitly approves the saved proposal for source acquisition.
|
|
30
|
-
|
|
30
|
+
A remote source is fetched during this second command. Inspect the resulting
|
|
31
31
|
update operations before the third command, which separately approves file writes.
|
|
32
32
|
The new origin becomes active as part of that normal, locked update, not as a
|
|
33
33
|
standalone write to `.pipeline/state.json`.
|
package/docs/repositories.md
CHANGED
|
@@ -29,8 +29,8 @@ workspace-pipeline wrap --workspace "C:\Work\GameWorkspace" --manifest "C:\Work\
|
|
|
29
29
|
|
|
30
30
|
Use `--json` and save the exact JSON stdout as UTF-8 `preview.json` outside the affected repository
|
|
31
31
|
and wrapper. Read its operations and blockers. Preview may acquire a temporary
|
|
32
|
-
Git snapshot, but does not perform repository effects.
|
|
33
|
-
|
|
32
|
+
Git snapshot, but does not perform repository effects. A selected remote source
|
|
33
|
+
is fetched during preparation; local Git is read from disk. Do not put credentials into
|
|
34
34
|
source URLs or retained preview files.
|
|
35
35
|
|
|
36
36
|
```powershell
|
package/package.json
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"additionalProperties": false,
|
|
5
|
+
"required": ["schemaVersion", "id", "version", "adapters"],
|
|
6
|
+
"properties": {
|
|
7
|
+
"schemaVersion": {"const": 2},
|
|
8
|
+
"id": {"type": "string", "pattern": "^[a-z][a-z0-9-]{0,62}$"},
|
|
9
|
+
"version": {"type": "string", "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$"},
|
|
10
|
+
"files": {"$ref": "#/properties/adapters/additionalProperties/properties/files"},
|
|
11
|
+
"adapters": {
|
|
12
|
+
"type": "object", "minProperties": 1,
|
|
13
|
+
"propertyNames": {"pattern": "^[a-z][a-z0-9-]{0,62}$"},
|
|
14
|
+
"additionalProperties": {
|
|
15
|
+
"type": "object", "additionalProperties": false,
|
|
16
|
+
"required": ["providers", "files", "settings"],
|
|
17
|
+
"properties": {
|
|
18
|
+
"providers": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"enum": ["codex", "claude", "grok", "kimi"]}},
|
|
19
|
+
"files": {"type": "array", "maxItems": 10000, "items": {
|
|
20
|
+
"type": "object", "additionalProperties": false,
|
|
21
|
+
"required": ["source", "target", "kind"],
|
|
22
|
+
"properties": {
|
|
23
|
+
"source": {"type": "string", "minLength": 1},
|
|
24
|
+
"target": {"type": "string", "minLength": 1},
|
|
25
|
+
"kind": {"enum": ["file", "directory"]}
|
|
26
|
+
}
|
|
27
|
+
}},
|
|
28
|
+
"settings": {"type": "array", "maxItems": 1000, "items": {
|
|
29
|
+
"type": "object", "additionalProperties": false,
|
|
30
|
+
"required": ["target", "pointer", "operation"],
|
|
31
|
+
"properties": {
|
|
32
|
+
"target": {"enum": ["codex.workspace", "claude.workspace", "claude.mcp", "grok.workspace", "grok.user"]},
|
|
33
|
+
"pointer": {"type": "string", "minLength": 2},
|
|
34
|
+
"operation": {"enum": ["set", "remove"]},
|
|
35
|
+
"value": {}
|
|
36
|
+
},
|
|
37
|
+
"allOf": [{"if": {"properties": {"operation": {"const": "set"}}}, "then": {"required": ["value"]}, "else": {"not": {"required": ["value"]}}}]
|
|
38
|
+
}}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/cli.js
CHANGED
|
@@ -3,6 +3,7 @@ import {providerRegistry} from './providers/registry.js';
|
|
|
3
3
|
import {outputWriter} from './commands/output.js';
|
|
4
4
|
import {createClaudeCompatibility} from './compat/claude.js';
|
|
5
5
|
import {runInteractiveUpdate} from './commands/interactive-update.js';
|
|
6
|
+
import {tryDesiredLifecycle} from './commands/desired-lifecycle.js';
|
|
6
7
|
import {createInterface} from 'node:readline/promises';
|
|
7
8
|
const write=stream=>text=>new Promise((resolve,reject)=>stream.write(text,error=>error?reject(error):resolve()));
|
|
8
9
|
// Prevent an unhandled pipe error; write callbacks report delivery failures.
|
|
@@ -10,14 +11,16 @@ process.stdout.on('error',()=>{});process.stderr.on('error',()=>{});
|
|
|
10
11
|
const args=process.argv.slice(2),json=args.includes('--json');
|
|
11
12
|
const options={stdout:outputWriter(write(process.stdout),json),stderr:outputWriter(write(process.stderr),json),registry:providerRegistry,compatibility:createClaudeCompatibility()};
|
|
12
13
|
try{
|
|
13
|
-
|
|
14
|
+
const interaction={
|
|
14
15
|
isTTY:Boolean(process.stdin.isTTY&&process.stderr.isTTY),display:write(process.stderr),
|
|
15
16
|
confirm:async()=>{
|
|
16
17
|
const rl=createInterface({input:process.stdin,output:process.stderr});
|
|
17
18
|
try{return /^(y|yes|д|да)$/i.test((await rl.question('Apply these changes? [y/N] ')).trim());}
|
|
18
19
|
catch{return false;}finally{rl.close();}
|
|
19
20
|
}
|
|
20
|
-
}
|
|
21
|
+
};
|
|
22
|
+
const desired=await tryDesiredLifecycle(args,options,interaction);
|
|
23
|
+
process.exitCode=desired??await runInteractiveUpdate(args,options,interaction);
|
|
21
24
|
}catch{
|
|
22
25
|
await options.stderr(JSON.stringify({error:'cli.interactive-io',next:'Inspect doctor before retry; no automatic retry or reset.'})+'\n');process.exitCode=2;
|
|
23
26
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import {homedir} from 'node:os';
|
|
3
|
+
import {ContractError,fail,parse} from '../contracts/parse.js';
|
|
4
|
+
import {absoluteRoot} from '../workspace/paths.js';
|
|
5
|
+
import {observeTargets} from '../operations/state.js';
|
|
6
|
+
import {utf8} from '../source/inventory.js';
|
|
7
|
+
import {prepareDesiredWorkspace} from '../desired-state/source.js';
|
|
8
|
+
import {materializeDesiredFiles,inspectDesiredFiles} from '../desired-state/inventory.js';
|
|
9
|
+
import {compileDesiredManifest} from '../contracts/desired-state.js';
|
|
10
|
+
import {readDesiredRecords} from '../desired-state/records.js';
|
|
11
|
+
import {includeRetiredTargets} from '../desired-state/retirement.js';
|
|
12
|
+
import {prepareLocalSettings} from '../desired-state/local-settings.js';
|
|
13
|
+
import {compileDesiredSettings} from '../desired-state/settings.js';
|
|
14
|
+
import {applyDesiredFiles} from '../desired-state/apply-files.js';
|
|
15
|
+
import {safeTerminalText as safe} from './output.js';
|
|
16
|
+
import {desiredArguments,initialDescriptor} from './desired-setup.js';
|
|
17
|
+
import {tryDesiredRemove} from './desired-remove.js';
|
|
18
|
+
import {prepareDesiredReset} from '../desired-state/reset.js';
|
|
19
|
+
import {runDesiredRecovery} from './desired-recover.js';
|
|
20
|
+
|
|
21
|
+
// V2 entrypoint; null preserves the old CLI path for old descriptors. No new
|
|
22
|
+
// source-supplied executable hooks, model changes or external preview required.
|
|
23
|
+
export async function tryDesiredLifecycle(args,options,{isTTY=false,confirm,display,hostOptions={}}={}) {
|
|
24
|
+
if(args[0]==='recover-lock')return runDesiredRecovery(args,options,{isTTY,confirm,display,hostOptions});
|
|
25
|
+
if(args[0]==='remove')return tryDesiredRemove(args,options,{isTTY,confirm,display,hostOptions});
|
|
26
|
+
if(!['setup','update','reset'].includes(args[0]) || args.includes('--apply'))return null;
|
|
27
|
+
const at=args.indexOf('--workspace');if(at<0 || !args[at+1])return null;
|
|
28
|
+
try {
|
|
29
|
+
const workspace=absoluteRoot(args[at+1]);
|
|
30
|
+
const [entry]=await observeTargets(workspace,['workspace.json']);
|
|
31
|
+
const creating=args[0]==='setup'&&args.includes('--source');
|
|
32
|
+
if(!creating&&entry.bytes===null)return null;
|
|
33
|
+
let descriptor=entry.bytes===null?null:parse(utf8(entry.bytes),'json');
|
|
34
|
+
if(!creating&&descriptor.schemaVersion!==2)return null;
|
|
35
|
+
const parsed=desiredArguments(args),{seen}=parsed;
|
|
36
|
+
if(creating) {
|
|
37
|
+
if(entry.bytes!==null)fail('desired.setup-already-configured');
|
|
38
|
+
descriptor=initialDescriptor(workspace,parsed,{cwd:hostOptions.cwd});
|
|
39
|
+
} else if(['--source','--adapters','--ref','--subdirectory','--repository','--documentation'].some(f=>seen.has(f)))fail('cli.arguments');
|
|
40
|
+
const dry=seen.has('--preview'),yes=seen.has('--yes');
|
|
41
|
+
if(dry&&yes)fail('cli.arguments');
|
|
42
|
+
if(!dry && !yes && (!isTTY||!confirm))fail('cli.confirmation-required');
|
|
43
|
+
const records=await readDesiredRecords(workspace,{allowLegacyMigration:true});
|
|
44
|
+
if(records.pending?.value.intent==='remove')fail('desired.finish-remove-before-update');
|
|
45
|
+
// Selecting a remote Git source and requesting setup/update authorizes the
|
|
46
|
+
// source fetch, not changes to the workspace before confirmation.
|
|
47
|
+
const prepare=args[0]==='reset'?prepareDesiredReset:prepareDesiredWorkspace;
|
|
48
|
+
const prepared=await prepare(workspace,JSON.stringify(descriptor),{
|
|
49
|
+
tempRoot:hostOptions.tempRoot,network:descriptor.pipeline?.transport==='remote'});
|
|
50
|
+
const compiled=compileDesiredManifest(prepared.input.manifest,{selected:prepared.input.selected,protectedPaths:prepared.input.protectedPaths});
|
|
51
|
+
const delivered=materializeDesiredFiles(compiled,prepared.input.source);
|
|
52
|
+
const previousRoots=Object.values(records.installed?.value.binding?.layout.repositories??{}).map(r=>r.path);
|
|
53
|
+
const protectedPaths=[...new Set([...prepared.input.protectedPaths,...previousRoots,...(records.legacy?.protectedPaths??[])])];
|
|
54
|
+
compileDesiredManifest(prepared.input.manifest,{selected:prepared.input.selected,protectedPaths});
|
|
55
|
+
const retired=includeRetiredTargets(records.installed?.value??records.legacy?.ownership,compiled,delivered,protectedPaths);
|
|
56
|
+
const files=await inspectDesiredFiles(workspace,retired.desired);
|
|
57
|
+
const local=await prepareLocalSettings(workspace,retired.settings.filter(s=>s.target!=='grok.user'));
|
|
58
|
+
const globalOps=retired.settings.filter(s=>s.target==='grok.user');let global=null;
|
|
59
|
+
if(globalOps.length) {
|
|
60
|
+
const home=absoluteRoot(hostOptions.userHome??homedir());
|
|
61
|
+
const [observed]=await observeTargets(home,['.grok/config.toml']);
|
|
62
|
+
const result=compileDesiredSettings('grok.user',observed.bytes,globalOps);
|
|
63
|
+
global={path:path.join(home,'.grok/config.toml'),changed:result.changed};
|
|
64
|
+
}
|
|
65
|
+
const summary={command:args[0],workspace,pipeline:compiled.pipeline,providers:compiled.providers,
|
|
66
|
+
files,localSettings:local.map(s=>({path:s.path,changed:s.changed})),globalSettings:global,
|
|
67
|
+
backup:seen.has('--backup'),legacyMigration:Boolean(records.legacy),...(prepared.reset?{reset:prepared.reset}:{}),descriptor:{path:'workspace.json',action:creating?'create':'preserve',layout:descriptor.layout},provenance:prepared.provenance,preparation:prepared.preparation};
|
|
68
|
+
if(dry){await options.stdout(JSON.stringify({...summary,status:'preview',applied:false})+'\n');return files.ready?0:1;}
|
|
69
|
+
const lines=[`Workspace Pipeline — ${args[0]}`,`Workspace: ${safe(workspace)}`,
|
|
70
|
+
`Pipeline: ${safe(compiled.pipeline.id)} @ ${safe(compiled.pipeline.version)}`,
|
|
71
|
+
`Backup: ${summary.backup?'enabled':'OFF'}`,
|
|
72
|
+
'The entire declared adapter scope will be replaced; custom files in it will be removed.'];
|
|
73
|
+
lines.push(`workspace.json: ${creating?'create':'preserve'}`);
|
|
74
|
+
if(prepared.reset)lines.push(`Reset: ${prepared.reset.mode}, Git revision ${safe(prepared.reset.commit)} (not latest).`);
|
|
75
|
+
for(const [id,repo] of Object.entries(descriptor.layout.repositories))lines.push(`Repository ${safe(id)}: ${safe(repo.path)} (not moved or cloned)`);
|
|
76
|
+
lines.push(`Documentation: ${safe(descriptor.layout.documentation.repository)} / ${safe(descriptor.layout.documentation.path)}`);
|
|
77
|
+
for(const category of ['extra','modified','missing','blocked'])for(const item of files[category])lines.push(` ${category}: ${safe(item.path)}`);
|
|
78
|
+
for(const item of summary.localSettings)lines.push(` settings: ${safe(item.path)} (${item.changed?'change':'unchanged'})`);
|
|
79
|
+
if(global)lines.push(`GLOBAL settings: ${safe(global.path)} (${global.changed?'change':'unchanged'}); affects other workspaces.`);
|
|
80
|
+
if(records.legacy)lines.push('Migrate legacy installation state; preserve its record in .pipeline/history and leave existing journals unchanged.');
|
|
81
|
+
if(display)await display(lines.join('\n')+'\n');else await options.stderr(lines.join('\n')+'\n');
|
|
82
|
+
if(!files.ready)fail('desired.unsafe-target');
|
|
83
|
+
if(!yes && await confirm()!==true){await options.stdout(JSON.stringify({status:'cancelled',applied:false})+'\n');return 0;}
|
|
84
|
+
// Apply the already acquired immutable source; do not fetch again after consent.
|
|
85
|
+
const result=await applyDesiredFiles({...prepared.input,backup:summary.backup},{...hostOptions,createDescriptor:creating,migrateLegacy:Boolean(records.legacy),expectedRecords:prepared.expectedRecords});
|
|
86
|
+
await options.stdout(JSON.stringify({...result,...(prepared.reset?{reset:prepared.reset}:{}),provenance:prepared.provenance,preparation:prepared.preparation})+'\n');return 0;
|
|
87
|
+
}catch(error){
|
|
88
|
+
await options.stderr(JSON.stringify({error:error instanceof ContractError?error.code:'cli.desired-io',
|
|
89
|
+
...(error.code==='desired.setup-already-configured'?{hint:'workspace.json already exists. Run doctor, then update using the saved declaration; do not repeat setup --source. If an installation was interrupted and the source ref moved, use reset. Finish pending removal with remove first.'}:{}),
|
|
90
|
+
...(error.code==='desired.finish-remove-before-update'?{hint:'Finish the pending removal with remove before setup, update or reset.'}:{}),
|
|
91
|
+
...(error.desiredState?{application:error.desiredState}:{})})+'\n');return 2;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {ContractError,fail} from '../contracts/parse.js';
|
|
2
|
+
import {desiredArguments} from './desired-setup.js';
|
|
3
|
+
import {inspectDesiredLock,recoverDesiredLock} from '../desired-state/lock-recovery.js';
|
|
4
|
+
import {safeTerminalText as safe} from './output.js';
|
|
5
|
+
|
|
6
|
+
export async function runDesiredRecovery(args,options,{isTTY=false,confirm,display,hostOptions={}}={}) {
|
|
7
|
+
try {
|
|
8
|
+
if(args.filter(a=>a==='--global').length>1)fail('cli.arguments');
|
|
9
|
+
const {seen,values}=desiredArguments(args.filter(a=>a!=='--global'));
|
|
10
|
+
if(!values.has('--workspace')||[...seen].some(f=>!['--workspace','--yes','--preview','--json'].includes(f)))fail('cli.arguments');
|
|
11
|
+
const dry=seen.has('--preview'),yes=seen.has('--yes');
|
|
12
|
+
if(dry&&yes)fail('cli.arguments');
|
|
13
|
+
if(!dry&&!yes&&(!isTTY||!confirm))fail('cli.confirmation-required');
|
|
14
|
+
const workspace=values.get('--workspace'),settings={userHome:hostOptions.userHome,global:args.includes('--global')};
|
|
15
|
+
const observed=await inspectDesiredLock(workspace,settings);
|
|
16
|
+
if(dry){await options.stdout(JSON.stringify({...observed,applied:false})+'\n');return observed.status==='owner-unconfirmed'?1:0;}
|
|
17
|
+
const text=['Workspace Pipeline — recover-lock',`Workspace: ${safe(observed.workspace)}`,
|
|
18
|
+
`Lock: ${safe(observed.path)}`,`Owner status: ${safe(observed.lock?.liveness??'absent')}`,
|
|
19
|
+
'Only stale lock metadata will be deleted; no process is killed and no installation is resumed.',
|
|
20
|
+
...(settings.global?['GLOBAL lock: user-wide Grok config lock; configuration remains unchanged.']:[])].join('\n')+'\n';
|
|
21
|
+
if(display)await display(text);else await options.stderr(text);
|
|
22
|
+
if(observed.status==='owner-unconfirmed')fail('desired.recovery-owner-unconfirmed');
|
|
23
|
+
if(!yes&&await confirm()!==true){await options.stdout(JSON.stringify({status:'cancelled',applied:false})+'\n');return 0;}
|
|
24
|
+
await options.stdout(JSON.stringify(await recoverDesiredLock(workspace,observed,settings))+'\n');return 0;
|
|
25
|
+
}catch(error){await options.stderr(JSON.stringify({error:error instanceof ContractError?error.code:'cli.desired-recovery-io',
|
|
26
|
+
...(error.recovery?{recovery:error.recovery}:{}),
|
|
27
|
+
next:'Inspect lock paths and pending state; do not delete unknown locks or operation records.'})+'\n');return 2;}
|
|
28
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {ContractError,fail} from '../contracts/parse.js';
|
|
2
|
+
import {absoluteRoot} from '../workspace/paths.js';
|
|
3
|
+
import {observeTargets} from '../operations/state.js';
|
|
4
|
+
import {readDesiredRecords,installedPath,pendingPath} from '../desired-state/records.js';
|
|
5
|
+
import {prepareDesiredRemoval} from '../desired-state/removal.js';
|
|
6
|
+
import {inspectDesiredFiles} from '../desired-state/inventory.js';
|
|
7
|
+
import {prepareLocalSettings} from '../desired-state/local-settings.js';
|
|
8
|
+
import {applyDesiredFiles} from '../desired-state/apply-files.js';
|
|
9
|
+
import {desiredArguments} from './desired-setup.js';
|
|
10
|
+
import {safeTerminalText as safe} from './output.js';
|
|
11
|
+
|
|
12
|
+
export async function tryDesiredRemove(args,options,{isTTY=false,confirm,display,hostOptions={}}={}) {
|
|
13
|
+
if(args[0]!=='remove')return null;
|
|
14
|
+
const at=args.indexOf('--workspace');if(at<0||!args[at+1])return null;
|
|
15
|
+
try {
|
|
16
|
+
const workspace=absoluteRoot(args[at+1]);
|
|
17
|
+
const markers=await observeTargets(workspace,[installedPath,pendingPath]);
|
|
18
|
+
if(markers.every(m=>m.bytes===null))return null;
|
|
19
|
+
const {seen}=desiredArguments(args);
|
|
20
|
+
if([...seen].some(f=>!['--workspace','--preview','--yes','--json','--backup'].includes(f)))fail('cli.arguments');
|
|
21
|
+
const dry=seen.has('--preview'),yes=seen.has('--yes');
|
|
22
|
+
if(dry&&yes)fail('cli.arguments');
|
|
23
|
+
if(!dry&&!yes&&(!isTTY||!confirm))fail('cli.confirmation-required');
|
|
24
|
+
const records=await readDesiredRecords(workspace),prepared=await prepareDesiredRemoval(workspace,records);
|
|
25
|
+
if(prepared.alreadyRemoved){await options.stdout(JSON.stringify({workspace,status:'removed',applied:false,globalSettings:'preserved'})+'\n');return 0;}
|
|
26
|
+
const files=await inspectDesiredFiles(workspace,prepared.desired);
|
|
27
|
+
const local=await prepareLocalSettings(workspace,prepared.settings);
|
|
28
|
+
const summary={command:'remove',workspace,status:'preview',applied:false,files,
|
|
29
|
+
localSettings:local.map(s=>({path:s.path,changed:s.changed})),backup:seen.has('--backup'),globalSettings:'preserved',
|
|
30
|
+
preserved:['workspace.json','project repositories','.pipeline journals and backups']};
|
|
31
|
+
if(dry){await options.stdout(JSON.stringify(summary)+'\n');return files.ready?0:1;}
|
|
32
|
+
const lines=['Workspace Pipeline — remove',`Workspace: ${safe(workspace)}`,`Backup: ${summary.backup?'enabled':'OFF'}`,
|
|
33
|
+
'Remove all recorded adapter files, including custom files inside owned directories.',
|
|
34
|
+
'Preserve repositories, workspace.json, journals/backups and shared global settings.'];
|
|
35
|
+
for(const item of files.extra)lines.push(` remove: ${safe(item.path)}`);
|
|
36
|
+
for(const item of files.blocked)lines.push(` blocked: ${safe(item.path)}`);
|
|
37
|
+
for(const item of summary.localSettings)lines.push(` settings: ${safe(item.path)} (${item.changed?'remove owned fields':'unchanged'})`);
|
|
38
|
+
if(display)await display(lines.join('\n')+'\n');else await options.stderr(lines.join('\n')+'\n');
|
|
39
|
+
if(!files.ready)fail('desired.unsafe-target');
|
|
40
|
+
if(!yes&&await confirm()!==true){await options.stdout(JSON.stringify({status:'cancelled',applied:false})+'\n');return 0;}
|
|
41
|
+
const result=await applyDesiredFiles({workspace,protectedPaths:prepared.protectedPaths,backup:summary.backup},
|
|
42
|
+
{...hostOptions,removeExisting:true,removalExpected:{installed:records.installed?.hash??null,pending:records.pending?.hash??null}});
|
|
43
|
+
await options.stdout(JSON.stringify({...result,workspace,preserved:summary.preserved})+'\n');return 0;
|
|
44
|
+
}catch(error){
|
|
45
|
+
await options.stderr(JSON.stringify({error:error instanceof ContractError?error.code:'cli.desired-remove-io',
|
|
46
|
+
...(error.desiredState?{application:error.desiredState}:{})})+'\n');return 2;
|
|
47
|
+
}
|
|
48
|
+
}
|