fastws-cli 0.0.5__tar.gz → 0.0.7__tar.gz
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.
- fastws_cli-0.0.7/CHANGELOG.md +31 -0
- {fastws_cli-0.0.5/fastws_cli.egg-info → fastws_cli-0.0.7}/PKG-INFO +2 -2
- {fastws_cli-0.0.5 → fastws_cli-0.0.7}/README.md +1 -1
- {fastws_cli-0.0.5 → fastws_cli-0.0.7}/fastws/__init__.py +1 -2
- {fastws_cli-0.0.5 → fastws_cli-0.0.7}/fastws/core.py +50 -34
- {fastws_cli-0.0.5 → fastws_cli-0.0.7/fastws_cli.egg-info}/PKG-INFO +2 -2
- {fastws_cli-0.0.5 → fastws_cli-0.0.7}/tests/test_sync.py +16 -7
- fastws_cli-0.0.5/CHANGELOG.md +0 -17
- {fastws_cli-0.0.5 → fastws_cli-0.0.7}/LICENSE +0 -0
- {fastws_cli-0.0.5 → fastws_cli-0.0.7}/MANIFEST.in +0 -0
- {fastws_cli-0.0.5 → fastws_cli-0.0.7}/fastws_cli.egg-info/SOURCES.txt +0 -0
- {fastws_cli-0.0.5 → fastws_cli-0.0.7}/fastws_cli.egg-info/dependency_links.txt +0 -0
- {fastws_cli-0.0.5 → fastws_cli-0.0.7}/fastws_cli.egg-info/entry_points.txt +0 -0
- {fastws_cli-0.0.5 → fastws_cli-0.0.7}/fastws_cli.egg-info/requires.txt +0 -0
- {fastws_cli-0.0.5 → fastws_cli-0.0.7}/fastws_cli.egg-info/top_level.txt +0 -0
- {fastws_cli-0.0.5 → fastws_cli-0.0.7}/pyproject.toml +0 -0
- {fastws_cli-0.0.5 → fastws_cli-0.0.7}/setup.cfg +0 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<!-- do not remove -->
|
|
2
|
+
|
|
3
|
+
## 0.0.7
|
|
4
|
+
|
|
5
|
+
### New Features
|
|
6
|
+
|
|
7
|
+
- Refactor Pyright editable path setup to write pyrightconfig.json instead of .pth files ([#4](https://github.com/AnswerDotAI/fastws/issues/4))
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## 0.0.6
|
|
11
|
+
|
|
12
|
+
### New Features
|
|
13
|
+
|
|
14
|
+
- Add root-aware clone/pull helpers, auto-pull in ws-sync, and docstrings to CLI entry points ([#2](https://github.com/AnswerDotAI/fastws/issues/2))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## 0.0.5
|
|
18
|
+
|
|
19
|
+
### New Features
|
|
20
|
+
|
|
21
|
+
- Add ws-sync and ws-add commands with workspace metadata sync and Pyright editable path support ([#1](https://github.com/AnswerDotAI/fastws/issues/1))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## 0.0.3
|
|
25
|
+
|
|
26
|
+
- change names
|
|
27
|
+
|
|
28
|
+
## 0.0.2
|
|
29
|
+
|
|
30
|
+
- init release
|
|
31
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastws-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.7
|
|
4
4
|
Summary: Fast workspace tools for multi-repo management
|
|
5
5
|
Author: Jeremy Howard
|
|
6
6
|
License: Apache-2.0
|
|
@@ -80,7 +80,7 @@ ws-branches --expected develop
|
|
|
80
80
|
|
|
81
81
|
### `ws-sync`
|
|
82
82
|
|
|
83
|
-
Sync the workspace metadata, install updates, and refresh Pyright editable paths.
|
|
83
|
+
Sync the workspace metadata, pull local repos, install updates, and refresh Pyright editable paths.
|
|
84
84
|
By default it uses the active venv parent as the workspace root, so you do not need to `cd` first:
|
|
85
85
|
It respects `tool.uv.workspace.members` and `exclude` when scanning local projects.
|
|
86
86
|
|
|
@@ -59,7 +59,7 @@ ws-branches --expected develop
|
|
|
59
59
|
|
|
60
60
|
### `ws-sync`
|
|
61
61
|
|
|
62
|
-
Sync the workspace metadata, install updates, and refresh Pyright editable paths.
|
|
62
|
+
Sync the workspace metadata, pull local repos, install updates, and refresh Pyright editable paths.
|
|
63
63
|
By default it uses the active venv parent as the workspace root, so you do not need to `cd` first:
|
|
64
64
|
It respects `tool.uv.workspace.members` and `exclude` when scanning local projects.
|
|
65
65
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
__all__ = ["ws_clone", "ws_clone_cli", "ws_pull", "ws_pull_cli", "ws_status", "ws_status_cli", "ws_branches", "ws_branches_cli",
|
|
4
4
|
"ws_sync", "ws_sync_cli", "ws_add", "ws_add_cli"]
|
|
5
5
|
|
|
6
|
-
import ast, fnmatch, os, re, shutil, subprocess
|
|
6
|
+
import ast, fnmatch, json, os, re, shutil, subprocess
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
9
9
|
|
|
@@ -193,35 +193,38 @@ def _site_packages(root: Path) -> Path|None:
|
|
|
193
193
|
if candidates: return candidates[0]
|
|
194
194
|
return None
|
|
195
195
|
|
|
196
|
-
def
|
|
196
|
+
def _write_pyright_config(root: Path):
|
|
197
197
|
site = _site_packages(root)
|
|
198
198
|
if not site:
|
|
199
199
|
print("No site-packages directory found for editable Pyright paths")
|
|
200
|
-
return
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
d
|
|
211
|
-
if Path(d).exists(): return f"✓ {d}: already exists"
|
|
200
|
+
return
|
|
201
|
+
paths = sorted({str(Path(p).parent) for f in site.glob("__editable__*_finder.py") for p in _editable_mapping(f).values()})
|
|
202
|
+
cfg_path = root / 'pyrightconfig.json'
|
|
203
|
+
cfg = json.loads(cfg_path.read_text()) if cfg_path.exists() else {}
|
|
204
|
+
cfg['extraPaths'] = paths
|
|
205
|
+
cfg_path.write_text(json.dumps(cfg, indent=2) + '\n')
|
|
206
|
+
for pth in site.glob('_pyright_editable_*.pth'): pth.unlink()
|
|
207
|
+
|
|
208
|
+
def _clone_one(repo: str, root: str = ".") -> str:
|
|
209
|
+
d = Path(root)/_repo_dir(repo)
|
|
210
|
+
if d.exists(): return f"✓ {d.name}: already exists"
|
|
212
211
|
try:
|
|
213
|
-
subprocess.run(["git", "clone", f"git@github.com:{repo}.git"], check=True, capture_output=True)
|
|
214
|
-
return f"✓ {d}: cloned"
|
|
215
|
-
except subprocess.CalledProcessError as e: return f"✗ {d}: {e.stderr.decode().strip()}"
|
|
212
|
+
subprocess.run(["git", "clone", f"git@github.com:{repo}.git", str(d)], check=True, capture_output=True)
|
|
213
|
+
return f"✓ {d.name}: cloned"
|
|
214
|
+
except subprocess.CalledProcessError as e: return f"✗ {d.name}: {e.stderr.decode().strip()}"
|
|
216
215
|
|
|
217
|
-
def _pull_one(repo: str) -> str:
|
|
218
|
-
d = _repo_dir(repo)
|
|
219
|
-
if not
|
|
216
|
+
def _pull_one(repo: str, root: str = ".") -> str:
|
|
217
|
+
d = Path(root)/_repo_dir(repo)
|
|
218
|
+
if not d.exists(): return f"✗ {d.name}: directory not found"
|
|
220
219
|
try:
|
|
221
|
-
res = subprocess.run(["git", "-C", d, "pull", "-q", "--stat"], check=True, capture_output=True, text=True)
|
|
222
|
-
return f"✓ {d}" + (f"\n{res.stdout.strip()}" if res.stdout.strip() else "")
|
|
220
|
+
res = subprocess.run(["git", "-C", str(d), "pull", "-q", "--stat"], check=True, capture_output=True, text=True)
|
|
221
|
+
return f"✓ {d.name}" + (f"\n{res.stdout.strip()}" if res.stdout.strip() else "")
|
|
223
222
|
except subprocess.CalledProcessError as e: return f"✗ {d}: {e.stderr.strip()}"
|
|
224
223
|
|
|
224
|
+
def _pull(repos: list[str], workers: int = 16, root: str = "."):
|
|
225
|
+
with ThreadPoolExecutor(max_workers=workers) as ex:
|
|
226
|
+
for result in as_completed([ex.submit(_pull_one, r, root) for r in repos]): print(result.result())
|
|
227
|
+
|
|
225
228
|
def ws_clone(
|
|
226
229
|
repos_file: str = "repos.txt", # File containing repo list (one per line: owner/repo)
|
|
227
230
|
workers: int = 16, # Number of parallel workers
|
|
@@ -235,22 +238,24 @@ def ws_clone(
|
|
|
235
238
|
def ws_clone_cli(
|
|
236
239
|
repos_file: str = "repos.txt", # File containing repo list (one per line: owner/repo)
|
|
237
240
|
workers: int = 16, # Number of parallel workers
|
|
238
|
-
):
|
|
241
|
+
):
|
|
242
|
+
"Clone all repos from a repos file."
|
|
243
|
+
ws_clone(repos_file, workers)
|
|
239
244
|
|
|
240
245
|
def ws_pull(
|
|
241
246
|
repos_file: str = "repos.txt", # File containing repo list
|
|
242
247
|
workers: int = 16, # Number of parallel workers
|
|
243
248
|
):
|
|
244
249
|
"Pull updates for all repos."
|
|
245
|
-
|
|
246
|
-
with ThreadPoolExecutor(max_workers=workers) as ex:
|
|
247
|
-
for result in as_completed([ex.submit(_pull_one, r) for r in repos]): print(result.result())
|
|
250
|
+
_pull(_load_repos(repos_file), workers)
|
|
248
251
|
|
|
249
252
|
@call_parse
|
|
250
253
|
def ws_pull_cli(
|
|
251
254
|
repos_file: str = "repos.txt", # File containing repo list
|
|
252
255
|
workers: int = 16, # Number of parallel workers
|
|
253
|
-
):
|
|
256
|
+
):
|
|
257
|
+
"Pull updates for all repos."
|
|
258
|
+
ws_pull(repos_file, workers)
|
|
254
259
|
|
|
255
260
|
def ws_status(
|
|
256
261
|
repos_file: str = "repos.txt", # File containing repo list
|
|
@@ -281,7 +286,9 @@ def ws_status(
|
|
|
281
286
|
def ws_status_cli(
|
|
282
287
|
repos_file: str = "repos.txt", # File containing repo list
|
|
283
288
|
branches: bool = False, # Show unpushed commit details
|
|
284
|
-
):
|
|
289
|
+
):
|
|
290
|
+
"Show uncommitted changes and optionally unpushed commit details across repos."
|
|
291
|
+
ws_status(repos_file, branches)
|
|
285
292
|
|
|
286
293
|
def ws_branches(
|
|
287
294
|
repos_file: str = "repos.txt", # File containing repo list
|
|
@@ -305,7 +312,9 @@ def ws_branches(
|
|
|
305
312
|
def ws_branches_cli(
|
|
306
313
|
repos_file: str = "repos.txt", # File containing repo list
|
|
307
314
|
expected: str = "main", # Expected branch name
|
|
308
|
-
):
|
|
315
|
+
):
|
|
316
|
+
"Check if all repos are on the expected branch."
|
|
317
|
+
ws_branches(repos_file, expected)
|
|
309
318
|
|
|
310
319
|
def ws_sync(
|
|
311
320
|
workspace: str = "", # Workspace root; defaults to active venv parent when available
|
|
@@ -313,18 +322,20 @@ def ws_sync(
|
|
|
313
322
|
pyproject_file: str = "pyproject.toml", # Workspace pyproject to update
|
|
314
323
|
template_file: str = "pyproject.tmpl", # Template copied when pyproject.toml is missing
|
|
315
324
|
):
|
|
316
|
-
"Sync workspace metadata, run uv sync -U, and
|
|
325
|
+
"Sync workspace metadata, run uv sync -U, and update Pyright config."
|
|
317
326
|
root = _ws_root(workspace, repos_file, pyproject_file, template_file)
|
|
318
327
|
repos_path = _resolve_path(root, repos_file)
|
|
319
328
|
pyproject_path = _resolve_path(root, pyproject_file)
|
|
320
329
|
template_path = _resolve_path(root, template_file)
|
|
330
|
+
repos = _discover_ws_repos(root)
|
|
321
331
|
|
|
322
|
-
if missing_repos := _update_repos_file(repos_path,
|
|
332
|
+
if missing_repos := _update_repos_file(repos_path, repos): print(f"Added repos: {', '.join(missing_repos)}")
|
|
333
|
+
_pull(repos, root=str(root))
|
|
323
334
|
|
|
324
335
|
if missing_projects := _sync_ws_pyproject(pyproject_path, template_path, _ws_projects(root)): print(f"Added workspace projects: {', '.join(missing_projects)}")
|
|
325
336
|
|
|
337
|
+
_write_pyright_config(root)
|
|
326
338
|
subprocess.run(["uv", "sync", "-U"], check=True, cwd=root)
|
|
327
|
-
_write_pyright_pth_files(root)
|
|
328
339
|
|
|
329
340
|
@call_parse
|
|
330
341
|
def ws_sync_cli(
|
|
@@ -332,7 +343,9 @@ def ws_sync_cli(
|
|
|
332
343
|
repos_file: str = "repos.txt", # Repo list to update from local git remotes
|
|
333
344
|
pyproject_file: str = "pyproject.toml", # Workspace pyproject to update
|
|
334
345
|
template_file: str = "pyproject.tmpl", # Template copied when pyproject.toml is missing
|
|
335
|
-
):
|
|
346
|
+
):
|
|
347
|
+
"Sync workspace metadata, run uv sync -U, and update Pyright config."
|
|
348
|
+
ws_sync(workspace, repos_file, pyproject_file, template_file)
|
|
336
349
|
|
|
337
350
|
def ws_add(
|
|
338
351
|
repo: str, # Repo to add, e.g. AnswerDotAI/fastws
|
|
@@ -348,6 +361,7 @@ def ws_add(
|
|
|
348
361
|
added = _update_repos_file(repos_path, [repo])
|
|
349
362
|
if added: print(f"Added repo: {repo}")
|
|
350
363
|
else: print(f"Repo already present: {repo}")
|
|
364
|
+
print(_clone_one(repo, str(root)))
|
|
351
365
|
ws_sync(str(root), repos_file, pyproject_file, template_file)
|
|
352
366
|
|
|
353
367
|
@call_parse
|
|
@@ -357,4 +371,6 @@ def ws_add_cli(
|
|
|
357
371
|
repos_file: str = "repos.txt", # Repo list to update
|
|
358
372
|
pyproject_file: str = "pyproject.toml", # Workspace pyproject to update
|
|
359
373
|
template_file: str = "pyproject.tmpl", # Template copied when pyproject.toml is missing
|
|
360
|
-
):
|
|
374
|
+
):
|
|
375
|
+
"Add a repo to repos.txt and then run ws-sync."
|
|
376
|
+
ws_add(repo, workspace, repos_file, pyproject_file, template_file)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastws-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.7
|
|
4
4
|
Summary: Fast workspace tools for multi-repo management
|
|
5
5
|
Author: Jeremy Howard
|
|
6
6
|
License: Apache-2.0
|
|
@@ -80,7 +80,7 @@ ws-branches --expected develop
|
|
|
80
80
|
|
|
81
81
|
### `ws-sync`
|
|
82
82
|
|
|
83
|
-
Sync the workspace metadata, install updates, and refresh Pyright editable paths.
|
|
83
|
+
Sync the workspace metadata, pull local repos, install updates, and refresh Pyright editable paths.
|
|
84
84
|
By default it uses the active venv parent as the workspace root, so you do not need to `cd` first:
|
|
85
85
|
It respects `tool.uv.workspace.members` and `exclude` when scanning local projects.
|
|
86
86
|
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
|
|
3
1
|
import fastws.core as core
|
|
4
2
|
|
|
5
3
|
|
|
@@ -98,6 +96,9 @@ def test_ws_sync_updates_workspace_and_runs_uv(tmp_path, monkeypatch):
|
|
|
98
96
|
if cmd[:5] == ["git", "-C", str(repo), "remote", "get-url"]:
|
|
99
97
|
class Res: stdout = "git@github.com:AnswerDotAI/repo1.git\n"
|
|
100
98
|
return Res()
|
|
99
|
+
if cmd[:4] == ["git", "-C", str(repo), "pull"]:
|
|
100
|
+
class Res: stdout = ""
|
|
101
|
+
return Res()
|
|
101
102
|
if cmd == ["uv", "sync", "-U"]:
|
|
102
103
|
class Res: stdout = ""
|
|
103
104
|
return Res()
|
|
@@ -112,6 +113,7 @@ def test_ws_sync_updates_workspace_and_runs_uv(tmp_path, monkeypatch):
|
|
|
112
113
|
assert 'newpkg = { workspace = true }' in pyproject
|
|
113
114
|
assert '"newpkg"' in pyproject
|
|
114
115
|
assert (site/"_pyright_editable_newpkg.pth").read_text() == "/tmp/ws/src/newpkg\n"
|
|
116
|
+
assert next(i for i,(cmd,_) in enumerate(calls) if cmd[:4] == ["git", "-C", str(repo), "pull"]) < next(i for i,(cmd,_) in enumerate(calls) if cmd == ["uv", "sync", "-U"])
|
|
115
117
|
assert any(cmd == ["uv", "sync", "-U"] and kwargs["cwd"] == tmp_path for cmd,kwargs in calls)
|
|
116
118
|
|
|
117
119
|
|
|
@@ -138,6 +140,9 @@ def test_ws_sync_uses_active_venv_parent_by_default(tmp_path, monkeypatch):
|
|
|
138
140
|
if cmd[:5] == ["git", "-C", str(repo), "remote", "get-url"]:
|
|
139
141
|
class Res: stdout = "git@github.com:AnswerDotAI/repo1.git\n"
|
|
140
142
|
return Res()
|
|
143
|
+
if cmd[:4] == ["git", "-C", str(repo), "pull"]:
|
|
144
|
+
class Res: stdout = ""
|
|
145
|
+
return Res()
|
|
141
146
|
if cmd == ["uv", "sync", "-U"]:
|
|
142
147
|
class Res: stdout = ""
|
|
143
148
|
return Res()
|
|
@@ -153,19 +158,23 @@ def test_ws_sync_uses_active_venv_parent_by_default(tmp_path, monkeypatch):
|
|
|
153
158
|
assert any(cmd == ["uv", "sync", "-U"] and kwargs["cwd"] == workspace for cmd,kwargs in calls)
|
|
154
159
|
|
|
155
160
|
|
|
156
|
-
def
|
|
161
|
+
def test_ws_add_clones_and_syncs(tmp_path, monkeypatch):
|
|
157
162
|
(tmp_path/"repos.txt").write_text("AnswerDotAI/existing\n")
|
|
158
|
-
|
|
163
|
+
sync_calls, clone_calls = [], []
|
|
159
164
|
|
|
160
|
-
def fake_sync(
|
|
161
|
-
|
|
165
|
+
def fake_sync(*a): sync_calls.append(a)
|
|
166
|
+
def fake_clone(repo, root="."):
|
|
167
|
+
clone_calls.append((repo, root))
|
|
168
|
+
return "✓ cloned"
|
|
162
169
|
|
|
163
170
|
monkeypatch.setattr(core, "ws_sync", fake_sync)
|
|
171
|
+
monkeypatch.setattr(core, "_clone_one", fake_clone)
|
|
164
172
|
|
|
165
173
|
core.ws_add("answerdotai/fastws", workspace=str(tmp_path))
|
|
166
174
|
|
|
167
175
|
assert (tmp_path/"repos.txt").read_text() == "AnswerDotAI/existing\nanswerdotai/fastws\n"
|
|
168
|
-
assert
|
|
176
|
+
assert clone_calls == [("answerdotai/fastws", str(tmp_path))]
|
|
177
|
+
assert sync_calls == [(str(tmp_path), "repos.txt", "pyproject.toml", "pyproject.tmpl")]
|
|
169
178
|
|
|
170
179
|
|
|
171
180
|
def test_ws_status_summarizes_unpushed_commits_by_default(tmp_path, monkeypatch, capsys):
|
fastws_cli-0.0.5/CHANGELOG.md
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
<!-- do not remove -->
|
|
2
|
-
|
|
3
|
-
## 0.0.5
|
|
4
|
-
|
|
5
|
-
### New Features
|
|
6
|
-
|
|
7
|
-
- Add ws-sync and ws-add commands with workspace metadata sync and Pyright editable path support ([#1](https://github.com/AnswerDotAI/fastws/issues/1))
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
## 0.0.3
|
|
11
|
-
|
|
12
|
-
- change names
|
|
13
|
-
|
|
14
|
-
## 0.0.2
|
|
15
|
-
|
|
16
|
-
- init release
|
|
17
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|