workbay 0.0.0__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.
- workbay-0.0.0/.gitignore +8 -0
- workbay-0.0.0/PKG-INFO +28 -0
- workbay-0.0.0/README.md +14 -0
- workbay-0.0.0/_scrub_core.py +51 -0
- workbay-0.0.0/hatch_build.py +288 -0
- workbay-0.0.0/pyproject.toml +41 -0
- workbay-0.0.0/workbay/__init__.py +8 -0
workbay-0.0.0/.gitignore
ADDED
workbay-0.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: workbay
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: WorkBay front-door name reservation. Ships no runtime code yet; install workbay-stack for the WorkBay runtime stack today.
|
|
5
|
+
Project-URL: Homepage, https://github.com/darce/workbay
|
|
6
|
+
Project-URL: Source, https://github.com/darce/workbay/tree/main/packages/workbay
|
|
7
|
+
Project-URL: Changelog, https://github.com/darce/workbay/blob/main/packages/workbay/CHANGELOG.md
|
|
8
|
+
Project-URL: Issues, https://github.com/darce/workbay/issues
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# workbay
|
|
16
|
+
|
|
17
|
+
Name reservation for the WorkBay front-door installer.
|
|
18
|
+
|
|
19
|
+
The single-command `workbay` installer is on its way. Until it lands,
|
|
20
|
+
install the runtime stack directly:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
pip install workbay-stack
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`workbay-stack` is a one-number version anchor that pulls every published
|
|
27
|
+
WorkBay runtime package. This `workbay` distribution carries no runtime code
|
|
28
|
+
yet and will become the single-command front door in a following release.
|
workbay-0.0.0/README.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# workbay
|
|
2
|
+
|
|
3
|
+
Name reservation for the WorkBay front-door installer.
|
|
4
|
+
|
|
5
|
+
The single-command `workbay` installer is on its way. Until it lands,
|
|
6
|
+
install the runtime stack directly:
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
pip install workbay-stack
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`workbay-stack` is a one-number version anchor that pulls every published
|
|
13
|
+
WorkBay runtime package. This `workbay` distribution carries no runtime code
|
|
14
|
+
yet and will become the single-command front door in a following release.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Build-time vendor of scripts/_scrub_core.py for hatch builds from sdist."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
INTERNAL_REF_PREFIXES = (
|
|
9
|
+
"AHMCP",
|
|
10
|
+
"AOMCP",
|
|
11
|
+
"APD",
|
|
12
|
+
"DCMCP",
|
|
13
|
+
"WBF",
|
|
14
|
+
"CHC",
|
|
15
|
+
"PA",
|
|
16
|
+
"MAINT",
|
|
17
|
+
"APM-SPEC-TR",
|
|
18
|
+
"WORKSTATE-REF",
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
INTERNAL_REF_RE = re.compile(
|
|
22
|
+
r"(?<![A-Za-z])(?:"
|
|
23
|
+
+ "|".join(re.escape(prefix) for prefix in INTERNAL_REF_PREFIXES)
|
|
24
|
+
+ r"|WS|WB|E[0-9]+)(?:[0-9]+)?(?:-[A-Z0-9]+)+"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
PROCESS_REF_RES = (
|
|
28
|
+
re.compile(r"\b[Pp]lan\s+[0-9]{4}\b"),
|
|
29
|
+
re.compile(r"\b[Ss]lice\s+[0-9]+[A-Za-z]?\b"),
|
|
30
|
+
re.compile(r"\b[Ss]tep\s+[0-9]+/[0-9]+\b"),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
INLINE_INTERNAL_PREFIX_RE = re.compile(
|
|
34
|
+
r"(?<![A-Za-z])(?:AHMCP|AOMCP|APD|DCMCP|WBF|CHC|APM-SPEC-TR)(?![A-Za-z])",
|
|
35
|
+
re.IGNORECASE,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
INLINE_EPIC_REF_RE = re.compile(r"E([0-9]+)-([0-9]+(?:-[A-Z0-9]+)*)")
|
|
39
|
+
|
|
40
|
+
_COLLAPSE_RE = re.compile(
|
|
41
|
+
r"\b(?:internal|implementation note)(?:[ \t]+(?:internal|implementation note))+\b"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def scrub_text(text: str) -> str:
|
|
46
|
+
scrubbed = INTERNAL_REF_RE.sub("internal", text)
|
|
47
|
+
scrubbed = INLINE_INTERNAL_PREFIX_RE.sub("internal", scrubbed)
|
|
48
|
+
scrubbed = INLINE_EPIC_REF_RE.sub("internal", scrubbed)
|
|
49
|
+
for regex in PROCESS_REF_RES:
|
|
50
|
+
scrubbed = regex.sub("implementation note", scrubbed)
|
|
51
|
+
return _COLLAPSE_RE.sub("internal", scrubbed)
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""Hatch build hook: scrub staged package copies into wheel+sdist artifacts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib.util
|
|
6
|
+
import shutil
|
|
7
|
+
import tempfile
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, NamedTuple, Protocol
|
|
10
|
+
|
|
11
|
+
try: # hatchling is a build-time-only dep (build-system.requires), not a test dep.
|
|
12
|
+
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
|
|
13
|
+
from hatchling.metadata.plugin.interface import MetadataHookInterface
|
|
14
|
+
from hatchling.plugin import hookimpl
|
|
15
|
+
except ModuleNotFoundError: # keep pure discover_scrub_targets importable for tests
|
|
16
|
+
BuildHookInterface = object # type: ignore[assignment,misc]
|
|
17
|
+
MetadataHookInterface = object # type: ignore[assignment,misc]
|
|
18
|
+
|
|
19
|
+
def hookimpl(func): # type: ignore[no-redef]
|
|
20
|
+
return func
|
|
21
|
+
|
|
22
|
+
_TEXT_SUFFIXES = {
|
|
23
|
+
".md",
|
|
24
|
+
".py",
|
|
25
|
+
".yaml",
|
|
26
|
+
".yml",
|
|
27
|
+
".json",
|
|
28
|
+
".toml",
|
|
29
|
+
".txt",
|
|
30
|
+
".mk",
|
|
31
|
+
".sh",
|
|
32
|
+
".in",
|
|
33
|
+
".cfg",
|
|
34
|
+
".ini",
|
|
35
|
+
".template",
|
|
36
|
+
".j2",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Build-support files vendored into the sdist so the wheel can be rebuilt from it.
|
|
40
|
+
# They carry the scrub matcher vocabulary verbatim (the literal AHMCP/AOMCP/... ids
|
|
41
|
+
# the transform looks for) and MUST NOT themselves be scrubbed — doing so rewrites
|
|
42
|
+
# those literals to "internal" and ships a gutted scrub engine. They ride into the
|
|
43
|
+
# sdist via hatchling's own only-include and are scan-exempt in the privacy gate
|
|
44
|
+
# (scripts/check_shipped_privacy.py: _SCAN_EXEMPT_RELPATHS), so skip them here.
|
|
45
|
+
_BUILD_SUPPORT_NAMES = {"_scrub_core.py", "hatch_build.py"}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ScrubTarget(NamedTuple):
|
|
49
|
+
source_path: Path
|
|
50
|
+
wheel_prefix: str
|
|
51
|
+
is_dir: bool
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class _WheelConfigView(Protocol):
|
|
55
|
+
packages: list[str] | None
|
|
56
|
+
only_include: list[str] | None
|
|
57
|
+
sources: list[str] | None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _strip_src_prefix(path: str) -> str:
|
|
61
|
+
if path.startswith("src/"):
|
|
62
|
+
return path.removeprefix("src/")
|
|
63
|
+
return path
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _rel_entry(entry: str, *, root: Path) -> str:
|
|
67
|
+
entry_path = Path(entry)
|
|
68
|
+
if entry_path.is_absolute():
|
|
69
|
+
return entry_path.relative_to(root).as_posix()
|
|
70
|
+
return entry.replace("\\", "/")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _wheel_prefix_for_entry(entry: str, *, root: Path) -> str:
|
|
74
|
+
rel = _rel_entry(entry, root=root)
|
|
75
|
+
if rel.endswith(".py"):
|
|
76
|
+
return Path(rel).name
|
|
77
|
+
return _strip_src_prefix(rel.rstrip("/"))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def discover_scrub_targets(
|
|
81
|
+
build_config: _WheelConfigView | Any,
|
|
82
|
+
*,
|
|
83
|
+
root: Path,
|
|
84
|
+
) -> list[ScrubTarget]:
|
|
85
|
+
"""Resolve layout-aware scrub targets from the wheel build config."""
|
|
86
|
+
packages = list(getattr(build_config, "packages", None) or [])
|
|
87
|
+
only_include = list(getattr(build_config, "only_include", None) or [])
|
|
88
|
+
sources = list(getattr(build_config, "sources", None) or [])
|
|
89
|
+
|
|
90
|
+
targets: list[ScrubTarget] = []
|
|
91
|
+
covered_sources: set[Path] = set()
|
|
92
|
+
|
|
93
|
+
def _append_target(source_path: Path, wheel_prefix: str) -> None:
|
|
94
|
+
resolved = source_path.resolve()
|
|
95
|
+
if resolved in covered_sources:
|
|
96
|
+
return
|
|
97
|
+
if not source_path.exists():
|
|
98
|
+
raise FileNotFoundError(source_path)
|
|
99
|
+
covered_sources.add(resolved)
|
|
100
|
+
targets.append(
|
|
101
|
+
ScrubTarget(
|
|
102
|
+
source_path=source_path,
|
|
103
|
+
wheel_prefix=wheel_prefix,
|
|
104
|
+
is_dir=source_path.is_dir(),
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
package_prefixes = {
|
|
109
|
+
_strip_src_prefix(_rel_entry(package, root=root).rstrip("/")) for package in packages
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
for package in packages:
|
|
113
|
+
rel = _rel_entry(package, root=root)
|
|
114
|
+
_append_target(root / rel, _strip_src_prefix(rel.rstrip("/")))
|
|
115
|
+
|
|
116
|
+
for entry in only_include:
|
|
117
|
+
rel = _rel_entry(entry, root=root)
|
|
118
|
+
if Path(rel).name in _BUILD_SUPPORT_NAMES:
|
|
119
|
+
# Vendored build machinery ships verbatim; never stage/scrub it.
|
|
120
|
+
continue
|
|
121
|
+
wheel_prefix = _wheel_prefix_for_entry(entry, root=root)
|
|
122
|
+
if rel.endswith(".py"):
|
|
123
|
+
source_path = root / rel
|
|
124
|
+
if not source_path.exists():
|
|
125
|
+
source_path = root / "src" / Path(rel).name
|
|
126
|
+
elif wheel_prefix in package_prefixes:
|
|
127
|
+
source_path = root / rel
|
|
128
|
+
else:
|
|
129
|
+
# Root-layout only-include names map to on-disk src/<pkg> trees.
|
|
130
|
+
candidate = root / "src" / wheel_prefix
|
|
131
|
+
source_path = candidate if candidate.exists() else root / rel
|
|
132
|
+
_append_target(source_path, wheel_prefix)
|
|
133
|
+
|
|
134
|
+
if targets:
|
|
135
|
+
return targets
|
|
136
|
+
|
|
137
|
+
if sources:
|
|
138
|
+
for source_root in sources:
|
|
139
|
+
rel = _rel_entry(source_root, root=root)
|
|
140
|
+
source_path = root / rel
|
|
141
|
+
if not source_path.exists():
|
|
142
|
+
raise FileNotFoundError(source_path)
|
|
143
|
+
wheel_prefix = _strip_src_prefix(rel.rstrip("/"))
|
|
144
|
+
targets.append(
|
|
145
|
+
ScrubTarget(
|
|
146
|
+
source_path=source_path,
|
|
147
|
+
wheel_prefix=wheel_prefix,
|
|
148
|
+
is_dir=source_path.is_dir(),
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
return targets
|
|
152
|
+
|
|
153
|
+
return targets
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _load_scrub_text():
|
|
157
|
+
scrub_core = Path(__file__).resolve().parent / "_scrub_core.py"
|
|
158
|
+
spec = importlib.util.spec_from_file_location("scrub_core_build", scrub_core)
|
|
159
|
+
assert spec and spec.loader
|
|
160
|
+
module = importlib.util.module_from_spec(spec)
|
|
161
|
+
spec.loader.exec_module(module)
|
|
162
|
+
return module.scrub_text
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _should_scrub(path: Path) -> bool:
|
|
166
|
+
return path.suffix.lower() in _TEXT_SUFFIXES or path.name in {
|
|
167
|
+
"Makefile",
|
|
168
|
+
"LICENSE",
|
|
169
|
+
"README.md",
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _scrub_tree(tree: Path, scrub_text) -> None:
|
|
174
|
+
for path in tree.rglob("*"):
|
|
175
|
+
if not path.is_file() or path.is_symlink():
|
|
176
|
+
continue
|
|
177
|
+
if not _should_scrub(path):
|
|
178
|
+
continue
|
|
179
|
+
try:
|
|
180
|
+
original = path.read_text(encoding="utf-8")
|
|
181
|
+
except (UnicodeDecodeError, OSError):
|
|
182
|
+
continue
|
|
183
|
+
scrubbed = scrub_text(original)
|
|
184
|
+
if scrubbed != original:
|
|
185
|
+
path.write_text(scrubbed, encoding="utf-8")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _stage_target(staging: Path, target: ScrubTarget, scrub_text) -> Path:
|
|
189
|
+
if target.is_dir:
|
|
190
|
+
staged = staging / target.wheel_prefix
|
|
191
|
+
# Drop compiled bytecode/caches: force_include bypasses the sdist `exclude`
|
|
192
|
+
# globs, so an uncleaned source tree would otherwise ship unscanned .pyc.
|
|
193
|
+
shutil.copytree(
|
|
194
|
+
target.source_path,
|
|
195
|
+
staged,
|
|
196
|
+
ignore=shutil.ignore_patterns("__pycache__", "*.pyc", "*.pyo"),
|
|
197
|
+
)
|
|
198
|
+
_scrub_tree(staged, scrub_text)
|
|
199
|
+
return staged
|
|
200
|
+
|
|
201
|
+
staged = staging / target.wheel_prefix
|
|
202
|
+
staged.parent.mkdir(parents=True, exist_ok=True)
|
|
203
|
+
shutil.copy2(target.source_path, staged)
|
|
204
|
+
if _should_scrub(staged):
|
|
205
|
+
original = staged.read_text(encoding="utf-8")
|
|
206
|
+
scrubbed = scrub_text(original)
|
|
207
|
+
if scrubbed != original:
|
|
208
|
+
staged.write_text(scrubbed, encoding="utf-8")
|
|
209
|
+
return staged
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _force_include_staged(
|
|
213
|
+
force_include: dict[str, str],
|
|
214
|
+
staged_root: Path,
|
|
215
|
+
wheel_prefix: str,
|
|
216
|
+
) -> None:
|
|
217
|
+
for path in staged_root.rglob("*"):
|
|
218
|
+
if not path.is_file():
|
|
219
|
+
continue
|
|
220
|
+
rel = path.relative_to(staged_root).as_posix()
|
|
221
|
+
force_include[str(path)] = f"{wheel_prefix}/{rel}" if rel else wheel_prefix
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class ScrubAtBuildHook(BuildHookInterface):
|
|
225
|
+
PLUGIN_NAME = "custom"
|
|
226
|
+
|
|
227
|
+
@property
|
|
228
|
+
def _staging_dirs(self) -> list[Path]:
|
|
229
|
+
dirs = getattr(self, "_staging_dirs_store", None)
|
|
230
|
+
if dirs is None:
|
|
231
|
+
dirs = []
|
|
232
|
+
self._staging_dirs_store = dirs
|
|
233
|
+
return dirs
|
|
234
|
+
|
|
235
|
+
def initialize(self, version: str, build_data: dict) -> None:
|
|
236
|
+
scrub_text = _load_scrub_text()
|
|
237
|
+
root = Path(self.root)
|
|
238
|
+
targets = discover_scrub_targets(self.build_config, root=root)
|
|
239
|
+
if not targets:
|
|
240
|
+
return
|
|
241
|
+
|
|
242
|
+
staging = Path(tempfile.mkdtemp(prefix="workbay-scrub-stage-", dir=None))
|
|
243
|
+
if not staging.is_absolute():
|
|
244
|
+
staging = staging.resolve()
|
|
245
|
+
self._staging_dirs.append(staging)
|
|
246
|
+
force_include = build_data.setdefault("force_include", {})
|
|
247
|
+
|
|
248
|
+
for target in targets:
|
|
249
|
+
staged = _stage_target(staging, target, scrub_text)
|
|
250
|
+
if target.is_dir:
|
|
251
|
+
_force_include_staged(force_include, staged, target.wheel_prefix)
|
|
252
|
+
else:
|
|
253
|
+
force_include[str(staged)] = target.wheel_prefix
|
|
254
|
+
|
|
255
|
+
def finalize(self, version: str, build_data: dict, artifact_path: str) -> None:
|
|
256
|
+
while self._staging_dirs:
|
|
257
|
+
shutil.rmtree(self._staging_dirs.pop(), ignore_errors=True)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class ScrubMetadataHook(MetadataHookInterface):
|
|
261
|
+
PLUGIN_NAME = "custom"
|
|
262
|
+
|
|
263
|
+
def update(self, metadata: dict) -> None:
|
|
264
|
+
"""Scrub the README long-description hatchling bakes into core metadata.
|
|
265
|
+
|
|
266
|
+
hatchling resolves ``readme`` -> the Description in wheel METADATA / sdist
|
|
267
|
+
PKG-INFO straight from the authored README, OUTSIDE the build hook's
|
|
268
|
+
force_include payload, so internal refs there would ship unscrubbed. A
|
|
269
|
+
package opts in by declaring ``dynamic = ["readme"]`` (and dropping the
|
|
270
|
+
static ``readme`` key); we supply the scrubbed long-description here.
|
|
271
|
+
"""
|
|
272
|
+
scrub_text = _load_scrub_text()
|
|
273
|
+
readme_path = Path(self.root) / "README.md"
|
|
274
|
+
if readme_path.is_file():
|
|
275
|
+
metadata["readme"] = {
|
|
276
|
+
"content-type": "text/markdown",
|
|
277
|
+
"text": scrub_text(readme_path.read_text(encoding="utf-8")),
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
@hookimpl
|
|
282
|
+
def hatch_register_build_hook():
|
|
283
|
+
return ScrubAtBuildHook
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
@hookimpl
|
|
287
|
+
def hatch_register_metadata_hook():
|
|
288
|
+
return ScrubMetadataHook
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.27"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "workbay"
|
|
7
|
+
version = "0.0.0"
|
|
8
|
+
description = "WorkBay front-door name reservation. Ships no runtime code yet; install workbay-stack for the WorkBay runtime stack today."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
# The front door itself floors at 3.11 (it carries no member pins, so it is not
|
|
11
|
+
# bound by the strictest member's 3.12 floor like workbay-stack is).
|
|
12
|
+
requires-python = ">=3.11"
|
|
13
|
+
license = "MIT"
|
|
14
|
+
|
|
15
|
+
[project.urls]
|
|
16
|
+
Homepage = "https://github.com/darce/workbay"
|
|
17
|
+
Source = "https://github.com/darce/workbay/tree/main/packages/workbay"
|
|
18
|
+
Changelog = "https://github.com/darce/workbay/blob/main/packages/workbay/CHANGELOG.md"
|
|
19
|
+
Issues = "https://github.com/darce/workbay/issues"
|
|
20
|
+
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
dev = [
|
|
23
|
+
"pytest>=8",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[tool.hatch.build.hooks.custom]
|
|
27
|
+
path = "hatch_build.py"
|
|
28
|
+
|
|
29
|
+
# Scrub-at-build shape mirrors workbay-stack: only-include the remapped IMPORT
|
|
30
|
+
# name (not src/<pkg>) so hatchling ships nothing raw and the hook's scrubbed
|
|
31
|
+
# force-include is the sole copy. Rebuild-safe via the sdist top-level <pkg>.
|
|
32
|
+
[tool.hatch.build.targets.wheel]
|
|
33
|
+
only-include = ["workbay"]
|
|
34
|
+
|
|
35
|
+
[tool.hatch.build.targets.sdist]
|
|
36
|
+
only-include = ["workbay", "hatch_build.py", "_scrub_core.py"]
|
|
37
|
+
exclude = ["**/__pycache__", "**/*.pyc"]
|
|
38
|
+
|
|
39
|
+
[tool.pytest.ini_options]
|
|
40
|
+
testpaths = ["tests"]
|
|
41
|
+
addopts = "-ra"
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Placeholder anchor module: ``workbay`` reserves the front-door name.
|
|
2
|
+
|
|
3
|
+
This distribution ships no runtime code yet. It reserves the ``workbay``
|
|
4
|
+
name on PyPI ahead of the single-command front-door installer. Install
|
|
5
|
+
``workbay-stack`` for the published WorkBay runtime stack today.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.0.0"
|