cdx-manager 0.9.1 → 0.9.2
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/changelogs/CHANGELOGS_0_9_2.md +33 -0
- package/checksums/release-archives.json +4 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/cli.py +2 -2
- package/src/cli_commands.py +5 -1
- package/src/session_service.py +38 -12
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# CDX Manager 0.9.2
|
|
2
|
+
|
|
3
|
+
## Highlights
|
|
4
|
+
|
|
5
|
+
- `cdx import` gains a `--merge` flag to fill in missing data without overwriting what already exists locally.
|
|
6
|
+
- npm and PyPI publication workflows now verify that the release tag's commit has a successful `CI` run before any registry upload step.
|
|
7
|
+
|
|
8
|
+
## Changes
|
|
9
|
+
|
|
10
|
+
### cdx import: `--merge` mode
|
|
11
|
+
|
|
12
|
+
`cdx import` previously offered two stances on existing sessions: reject the conflict (default) or erase and replace (`--force`). A third mode is now available:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
cdx import backup.cdx --merge
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
With `--merge`, for each session that already exists locally:
|
|
19
|
+
|
|
20
|
+
- **Session fields** — existing values are kept; fields absent locally are pulled in from the bundle.
|
|
21
|
+
- **Session state** — same merge rule: local data wins, bundle fills the gaps.
|
|
22
|
+
- **Profile files** (auth.json, credentials, etc.) — files that already exist on disk are left untouched; files missing locally are restored from the bundle.
|
|
23
|
+
- **New sessions** in the bundle that have no local counterpart are imported normally.
|
|
24
|
+
|
|
25
|
+
`--force` and `--merge` are mutually exclusive and raise a clear error if combined.
|
|
26
|
+
|
|
27
|
+
## Validation
|
|
28
|
+
|
|
29
|
+
- `npm run prepublishOnly`
|
|
30
|
+
- `npm pack --dry-run`
|
|
31
|
+
- `python -m unittest discover -s test -p 'test_release_ci_py.py'`
|
|
32
|
+
- `logics-manager lint --require-status`
|
|
33
|
+
- `logics-manager audit --legacy-cutoff-version 1.1.0 --group-by-doc`
|
|
@@ -68,6 +68,10 @@
|
|
|
68
68
|
"v0.9.0": {
|
|
69
69
|
"github_tarball_sha256": "809af5746e1287f4c5dc7a2fb7583e67e212aa250c45be905c32374e5dee26d6",
|
|
70
70
|
"github_zip_sha256": "9118f81645c00a4a660923a1ae290f48bd458af7ebfcf9f6580a3b91023b7c76"
|
|
71
|
+
},
|
|
72
|
+
"v0.9.1": {
|
|
73
|
+
"github_tarball_sha256": "8cbab620c823aeaf56e72c1a4d20e251a1c7142bfeeb3d516321d59c2c0a621d",
|
|
74
|
+
"github_zip_sha256": "253909ede6dca61937835bd4ee29145ddacaa08fbdfba1b39810da73ae2ccc84"
|
|
71
75
|
}
|
|
72
76
|
}
|
|
73
77
|
}
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
package/src/cli.py
CHANGED
|
@@ -64,7 +64,7 @@ from .status_view import (
|
|
|
64
64
|
)
|
|
65
65
|
from .update_check import check_for_update, check_logics_manager_for_update
|
|
66
66
|
|
|
67
|
-
VERSION = "0.9.
|
|
67
|
+
VERSION = "0.9.2"
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
# ---------------------------------------------------------------------------
|
|
@@ -108,7 +108,7 @@ def _print_help(use_color=False):
|
|
|
108
108
|
f" {_style('cdx rmv <name> [--force] [--json]', '36', use_color)}",
|
|
109
109
|
f" {_style('cdx clean [name] [--json]', '36', use_color)}",
|
|
110
110
|
f" {_style('cdx export <file> [--include-auth] [--sessions a,b] [--passphrase-env VAR] [--force] [--json]', '36', use_color)}",
|
|
111
|
-
f" {_style('cdx import <file> [--sessions a,b] [--passphrase-env VAR] [--force] [--json]', '36', use_color)}",
|
|
111
|
+
f" {_style('cdx import <file> [--sessions a,b] [--passphrase-env VAR] [--force|--merge] [--json]', '36', use_color)}",
|
|
112
112
|
f" {_style('cdx doctor [--json]', '36', use_color)}",
|
|
113
113
|
f" {_style('cdx repair [--dry-run] [--force] [--json]', '36', use_color)}",
|
|
114
114
|
f" {_style('cdx view [--json]', '36', use_color)}",
|
package/src/cli_commands.py
CHANGED
|
@@ -55,7 +55,7 @@ DOCTOR_USAGE = "Usage: cdx doctor [--json]"
|
|
|
55
55
|
REPAIR_USAGE = "Usage: cdx repair [--dry-run] [--force] [--json]"
|
|
56
56
|
UPDATE_USAGE = "Usage: cdx update [--check] [--yes] [--json] [--version TAG]"
|
|
57
57
|
EXPORT_USAGE = "Usage: cdx export <file> [--include-auth] [--force] [--json] [--sessions name1,name2] [--passphrase-env VAR]"
|
|
58
|
-
IMPORT_USAGE = "Usage: cdx import <file> [--force] [--json] [--sessions name1,name2] [--passphrase-env VAR]"
|
|
58
|
+
IMPORT_USAGE = "Usage: cdx import <file> [--force|--merge] [--json] [--sessions name1,name2] [--passphrase-env VAR]"
|
|
59
59
|
CONTEXT_USAGE = "Usage: cdx context show|path|init|edit|clear|set [text...] [--json]"
|
|
60
60
|
HANDOFF_USAGE = "Usage: cdx handoff <name> [--json] | cdx handoff <source> <target> [--json]"
|
|
61
61
|
SET_USAGE = "Usage: cdx set <name>|--sessions all|a,b|--provider PROVIDER [--power low|medium|high|xhigh|max] [--permission review|default|auto|full] [--fast on|off] [--rtk on|off] [--logics on|off] [--model MODEL] [--priority 0..100] [--json]"
|
|
@@ -1000,6 +1000,7 @@ def _parse_export_args(args):
|
|
|
1000
1000
|
def _parse_import_args(args):
|
|
1001
1001
|
parsed = _parse_flag_args(args, {
|
|
1002
1002
|
"--force": {"key": "force", "type": "bool", "default": False},
|
|
1003
|
+
"--merge": {"key": "merge", "type": "bool", "default": False},
|
|
1003
1004
|
"--json": {"key": "json", "type": "bool", "default": False},
|
|
1004
1005
|
"--sessions": {
|
|
1005
1006
|
"key": "session_names",
|
|
@@ -1012,6 +1013,8 @@ def _parse_import_args(args):
|
|
|
1012
1013
|
parsed["file_path"] = parsed.pop("positionals")[0] if parsed["positionals"] else None
|
|
1013
1014
|
if not parsed["file_path"]:
|
|
1014
1015
|
raise CdxError(IMPORT_USAGE)
|
|
1016
|
+
if parsed["force"] and parsed["merge"]:
|
|
1017
|
+
raise CdxError("--force and --merge are mutually exclusive.")
|
|
1015
1018
|
return parsed
|
|
1016
1019
|
|
|
1017
1020
|
|
|
@@ -2643,6 +2646,7 @@ def handle_import(rest, ctx):
|
|
|
2643
2646
|
passphrase=passphrase,
|
|
2644
2647
|
session_names=parsed["session_names"],
|
|
2645
2648
|
force=parsed["force"],
|
|
2649
|
+
merge=parsed["merge"],
|
|
2646
2650
|
)
|
|
2647
2651
|
session_count = len(result["session_names"])
|
|
2648
2652
|
auth_suffix = " with auth" if result["include_auth"] else ""
|
package/src/session_service.py
CHANGED
|
@@ -1173,7 +1173,7 @@ def create_session_service(options=None):
|
|
|
1173
1173
|
"bundle_size_bytes": len(bundle_bytes),
|
|
1174
1174
|
}
|
|
1175
1175
|
|
|
1176
|
-
def import_bundle(file_path, passphrase=None, session_names=None, force=False):
|
|
1176
|
+
def import_bundle(file_path, passphrase=None, session_names=None, force=False, merge=False):
|
|
1177
1177
|
if not file_path or not os.path.isfile(file_path):
|
|
1178
1178
|
raise CdxError(f"Bundle file not found: {file_path}")
|
|
1179
1179
|
with open(file_path, "rb") as handle:
|
|
@@ -1193,15 +1193,18 @@ def create_session_service(options=None):
|
|
|
1193
1193
|
|
|
1194
1194
|
existing = {session["name"] for session in list_sessions()}
|
|
1195
1195
|
conflicts = [name for name in names if name in existing]
|
|
1196
|
-
if conflicts and not force:
|
|
1196
|
+
if conflicts and not force and not merge:
|
|
1197
1197
|
raise CdxError(f"Import would overwrite existing sessions: {', '.join(conflicts)}")
|
|
1198
1198
|
|
|
1199
1199
|
for session_payload in imported_sessions:
|
|
1200
1200
|
name = session_payload["name"]
|
|
1201
1201
|
_validate_new_session_name(name)
|
|
1202
1202
|
provider = _normalize_provider(session_payload["provider"])
|
|
1203
|
-
|
|
1203
|
+
is_existing = name in existing
|
|
1204
|
+
|
|
1205
|
+
if is_existing and force:
|
|
1204
1206
|
remove_session(name)
|
|
1207
|
+
is_existing = False
|
|
1205
1208
|
|
|
1206
1209
|
session_root = _get_session_root(name)
|
|
1207
1210
|
auth_home = _get_session_auth_home(name, provider)
|
|
@@ -1210,18 +1213,38 @@ def create_session_service(options=None):
|
|
|
1210
1213
|
_ensure_private_dir(session_root)
|
|
1211
1214
|
_ensure_private_dir(auth_home)
|
|
1212
1215
|
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1216
|
+
if is_existing and merge:
|
|
1217
|
+
existing_record = store["get_session"](name) or {}
|
|
1218
|
+
bundle_record = {
|
|
1219
|
+
**session_payload,
|
|
1220
|
+
"provider": provider,
|
|
1221
|
+
"enabled": session_payload.get("enabled", True) is not False,
|
|
1222
|
+
"sessionRoot": session_root,
|
|
1223
|
+
"authHome": auth_home,
|
|
1224
|
+
}
|
|
1225
|
+
# Existing values take precedence; bundle fills in missing keys only.
|
|
1226
|
+
merged_record = {**bundle_record, **{k: v for k, v in existing_record.items() if v is not None}}
|
|
1227
|
+
merged_record["sessionRoot"] = session_root
|
|
1228
|
+
merged_record["authHome"] = auth_home
|
|
1229
|
+
store["replace_session"](name, merged_record)
|
|
1230
|
+
else:
|
|
1231
|
+
session_record = {
|
|
1232
|
+
**session_payload,
|
|
1233
|
+
"provider": provider,
|
|
1234
|
+
"enabled": session_payload.get("enabled", True) is not False,
|
|
1235
|
+
"sessionRoot": session_root,
|
|
1236
|
+
"authHome": auth_home,
|
|
1237
|
+
}
|
|
1238
|
+
store["replace_session"](name, session_record)
|
|
1221
1239
|
|
|
1222
1240
|
state = (payload.get("states") or {}).get(name)
|
|
1223
1241
|
if state is not None:
|
|
1224
|
-
|
|
1242
|
+
if is_existing and merge:
|
|
1243
|
+
existing_state = store["read_session_state"](name) or {}
|
|
1244
|
+
merged_state = {**state, **{k: v for k, v in existing_state.items() if v is not None}}
|
|
1245
|
+
store["write_session_state"](name, merged_state)
|
|
1246
|
+
else:
|
|
1247
|
+
store["write_session_state"](name, state)
|
|
1225
1248
|
|
|
1226
1249
|
for item in (payload.get("profiles") or {}).get(name, []):
|
|
1227
1250
|
rel_path = _safe_relpath(item.get("path"))
|
|
@@ -1230,6 +1253,9 @@ def create_session_service(options=None):
|
|
|
1230
1253
|
except (AttributeError, ValueError, UnicodeEncodeError) as error:
|
|
1231
1254
|
raise CdxError(f"Bundle contains invalid file data for session {name}: {rel_path}") from error
|
|
1232
1255
|
dest_path = os.path.join(session_root, rel_path)
|
|
1256
|
+
# In merge mode, skip files that already exist locally.
|
|
1257
|
+
if is_existing and merge and os.path.exists(dest_path):
|
|
1258
|
+
continue
|
|
1233
1259
|
_ensure_private_dir(os.path.dirname(dest_path))
|
|
1234
1260
|
with open(dest_path, "wb") as handle:
|
|
1235
1261
|
handle.write(content)
|