minutework 0.1.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/EXTERNAL_ALPHA.md +74 -0
- package/README.md +57 -0
- package/assets/claude-local/CLAUDE.md.template +45 -0
- package/assets/claude-local/bundle.json +22 -0
- package/assets/claude-local/skills/README.md +6 -0
- package/assets/claude-local/skills/app-pack-authoring.md +8 -0
- package/assets/claude-local/skills/event-bus.md +8 -0
- package/assets/claude-local/skills/ontology-mapping.md +8 -0
- package/assets/claude-local/skills/openclaw-skill-importer.md +7 -0
- package/assets/claude-local/skills/schema-engine.md +8 -0
- package/assets/claude-local/skills/secrets-runtime-bridge.md +9 -0
- package/assets/claude-local/skills/sidecar-generation.md +9 -0
- package/assets/templates/fastapi-sidecar/.env.example +8 -0
- package/assets/templates/fastapi-sidecar/README.md +77 -0
- package/assets/templates/fastapi-sidecar/poetry.lock +757 -0
- package/assets/templates/fastapi-sidecar/pyproject.toml +42 -0
- package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/__init__.py +3 -0
- package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/auth.py +70 -0
- package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/bridge/__init__.py +3 -0
- package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/bridge/client.py +71 -0
- package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/logging_utils.py +25 -0
- package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/main.py +85 -0
- package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/receipts.py +24 -0
- package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/settings.py +41 -0
- package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/template_validation.py +26 -0
- package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/worker.py +33 -0
- package/assets/templates/fastapi-sidecar/template.json +43 -0
- package/assets/templates/fastapi-sidecar/template.schema.json +160 -0
- package/assets/templates/fastapi-sidecar/tests/conftest.py +36 -0
- package/assets/templates/fastapi-sidecar/tests/test_app.py +39 -0
- package/assets/templates/fastapi-sidecar/tests/test_auth.py +32 -0
- package/assets/templates/fastapi-sidecar/tests/test_bridge_client.py +31 -0
- package/assets/templates/fastapi-sidecar/tests/test_materialization.py +55 -0
- package/assets/templates/fastapi-sidecar/tests/test_template_contract.py +49 -0
- package/assets/templates/fastapi-sidecar/tests/test_worker.py +7 -0
- package/assets/templates/fastapi-sidecar/tools/template/validate_template.py +20 -0
- package/assets/templates/next-tenant-app/.env.example +8 -0
- package/assets/templates/next-tenant-app/.storybook/main.ts +19 -0
- package/assets/templates/next-tenant-app/.storybook/preview.tsx +38 -0
- package/assets/templates/next-tenant-app/README.md +115 -0
- package/assets/templates/next-tenant-app/components.json +21 -0
- package/assets/templates/next-tenant-app/eslint.config.mjs +41 -0
- package/assets/templates/next-tenant-app/next-env.d.ts +6 -0
- package/assets/templates/next-tenant-app/next.config.ts +8 -0
- package/assets/templates/next-tenant-app/package-lock.json +9682 -0
- package/assets/templates/next-tenant-app/package.json +59 -0
- package/assets/templates/next-tenant-app/pnpm-lock.yaml +6062 -0
- package/assets/templates/next-tenant-app/postcss.config.mjs +8 -0
- package/assets/templates/next-tenant-app/src/app/api/auth/context/route.test.ts +90 -0
- package/assets/templates/next-tenant-app/src/app/api/auth/context/route.ts +78 -0
- package/assets/templates/next-tenant-app/src/app/api/auth/login/route.ts +31 -0
- package/assets/templates/next-tenant-app/src/app/api/auth/logout/route.ts +16 -0
- package/assets/templates/next-tenant-app/src/app/api/auth/password-change/route.test.ts +79 -0
- package/assets/templates/next-tenant-app/src/app/api/auth/password-change/route.ts +40 -0
- package/assets/templates/next-tenant-app/src/app/api/auth/password-status/route.test.ts +42 -0
- package/assets/templates/next-tenant-app/src/app/api/auth/password-status/route.ts +29 -0
- package/assets/templates/next-tenant-app/src/app/api/auth/session/route.ts +26 -0
- package/assets/templates/next-tenant-app/src/app/api/gateway/commands/[runId]/route.test.ts +40 -0
- package/assets/templates/next-tenant-app/src/app/api/gateway/commands/[runId]/route.ts +47 -0
- package/assets/templates/next-tenant-app/src/app/api/gateway/commands/route.test.ts +43 -0
- package/assets/templates/next-tenant-app/src/app/api/gateway/commands/route.ts +45 -0
- package/assets/templates/next-tenant-app/src/app/app/examples/runtime-commands/page.test.ts +83 -0
- package/assets/templates/next-tenant-app/src/app/app/examples/runtime-commands/page.tsx +30 -0
- package/assets/templates/next-tenant-app/src/app/app/layout.tsx +20 -0
- package/assets/templates/next-tenant-app/src/app/app/page.test.ts +62 -0
- package/assets/templates/next-tenant-app/src/app/app/page.tsx +24 -0
- package/assets/templates/next-tenant-app/src/app/blog/[slug]/page.test.ts +70 -0
- package/assets/templates/next-tenant-app/src/app/blog/[slug]/page.tsx +57 -0
- package/assets/templates/next-tenant-app/src/app/blog/page.test.ts +42 -0
- package/assets/templates/next-tenant-app/src/app/blog/page.tsx +37 -0
- package/assets/templates/next-tenant-app/src/app/docs/[...slug]/page.test.ts +70 -0
- package/assets/templates/next-tenant-app/src/app/docs/[...slug]/page.tsx +55 -0
- package/assets/templates/next-tenant-app/src/app/docs/page.test.ts +42 -0
- package/assets/templates/next-tenant-app/src/app/docs/page.tsx +37 -0
- package/assets/templates/next-tenant-app/src/app/globals.css +70 -0
- package/assets/templates/next-tenant-app/src/app/layout.tsx +69 -0
- package/assets/templates/next-tenant-app/src/app/login/page.test.ts +55 -0
- package/assets/templates/next-tenant-app/src/app/login/page.tsx +33 -0
- package/assets/templates/next-tenant-app/src/app/page.test.ts +56 -0
- package/assets/templates/next-tenant-app/src/app/page.tsx +35 -0
- package/assets/templates/next-tenant-app/src/app/pricing/page.test.ts +55 -0
- package/assets/templates/next-tenant-app/src/app/pricing/page.tsx +35 -0
- package/assets/templates/next-tenant-app/src/app/providers.tsx +25 -0
- package/assets/templates/next-tenant-app/src/app/robots.test.ts +20 -0
- package/assets/templates/next-tenant-app/src/app/robots.ts +18 -0
- package/assets/templates/next-tenant-app/src/app/sitemap.test.ts +49 -0
- package/assets/templates/next-tenant-app/src/app/sitemap.ts +54 -0
- package/assets/templates/next-tenant-app/src/components/ui/button.tsx +59 -0
- package/assets/templates/next-tenant-app/src/components/ui/input.tsx +21 -0
- package/assets/templates/next-tenant-app/src/design-system/docs/governance.mdx +26 -0
- package/assets/templates/next-tenant-app/src/design-system/patterns/panel-frame.stories.tsx +48 -0
- package/assets/templates/next-tenant-app/src/design-system/patterns/panel-frame.tsx +26 -0
- package/assets/templates/next-tenant-app/src/design-system/patterns/status-badge.stories.tsx +26 -0
- package/assets/templates/next-tenant-app/src/design-system/patterns/status-badge.tsx +35 -0
- package/assets/templates/next-tenant-app/src/design-system/patterns/theme-mode-toggle.stories.tsx +21 -0
- package/assets/templates/next-tenant-app/src/design-system/patterns/theme-mode-toggle.tsx +75 -0
- package/assets/templates/next-tenant-app/src/design-system/primitives/button.stories.tsx +37 -0
- package/assets/templates/next-tenant-app/src/design-system/primitives/button.ts +1 -0
- package/assets/templates/next-tenant-app/src/design-system/primitives/input.stories.tsx +26 -0
- package/assets/templates/next-tenant-app/src/design-system/primitives/input.ts +1 -0
- package/assets/templates/next-tenant-app/src/design-system/recipes/chrome.ts +28 -0
- package/assets/templates/next-tenant-app/src/design-system/tokens/foundation.css +31 -0
- package/assets/templates/next-tenant-app/src/design-system/tokens/index.css +3 -0
- package/assets/templates/next-tenant-app/src/design-system/tokens/manifest.json +85 -0
- package/assets/templates/next-tenant-app/src/design-system/tokens/manifest.ts +87 -0
- package/assets/templates/next-tenant-app/src/design-system/tokens/semantic.css +105 -0
- package/assets/templates/next-tenant-app/src/design-system/tokens/theme.css +59 -0
- package/assets/templates/next-tenant-app/src/design-system/tokens/tokens.stories.tsx +71 -0
- package/assets/templates/next-tenant-app/src/features/auth/components/login-screen.tsx +198 -0
- package/assets/templates/next-tenant-app/src/features/dashboard/components/tenant-dashboard.tsx +153 -0
- package/assets/templates/next-tenant-app/src/features/examples/runtime-command-demo/components/runtime-command-demo.tsx +342 -0
- package/assets/templates/next-tenant-app/src/features/public-shell/components/content-article.tsx +66 -0
- package/assets/templates/next-tenant-app/src/features/public-shell/components/content-collection.tsx +108 -0
- package/assets/templates/next-tenant-app/src/features/public-shell/components/marketing-page-canvas.tsx +111 -0
- package/assets/templates/next-tenant-app/src/features/public-shell/components/public-site-shell.tsx +111 -0
- package/assets/templates/next-tenant-app/src/features/shell/components/private-app-shell.tsx +624 -0
- package/assets/templates/next-tenant-app/src/lib/app-routes.test.ts +20 -0
- package/assets/templates/next-tenant-app/src/lib/app-routes.ts +59 -0
- package/assets/templates/next-tenant-app/src/lib/content/__fixtures__/public-site-snapshot.ts +189 -0
- package/assets/templates/next-tenant-app/src/lib/content/adapter.server.test.ts +318 -0
- package/assets/templates/next-tenant-app/src/lib/content/adapter.server.ts +232 -0
- package/assets/templates/next-tenant-app/src/lib/content/contracts.ts +339 -0
- package/assets/templates/next-tenant-app/src/lib/content/custom-adapter.ts +5 -0
- package/assets/templates/next-tenant-app/src/lib/content/empty-state.ts +96 -0
- package/assets/templates/next-tenant-app/src/lib/platform/auth.server.test.ts +75 -0
- package/assets/templates/next-tenant-app/src/lib/platform/auth.server.ts +25 -0
- package/assets/templates/next-tenant-app/src/lib/platform/client.server.test.ts +170 -0
- package/assets/templates/next-tenant-app/src/lib/platform/client.server.ts +661 -0
- package/assets/templates/next-tenant-app/src/lib/platform/contracts.ts +131 -0
- package/assets/templates/next-tenant-app/src/lib/platform/endpoints.server.ts +34 -0
- package/assets/templates/next-tenant-app/src/lib/platform/env.server.test.ts +102 -0
- package/assets/templates/next-tenant-app/src/lib/platform/env.server.ts +87 -0
- package/assets/templates/next-tenant-app/src/lib/platform/route-response.ts +33 -0
- package/assets/templates/next-tenant-app/src/lib/platform/session.server.ts +108 -0
- package/assets/templates/next-tenant-app/src/lib/public-site.test.ts +20 -0
- package/assets/templates/next-tenant-app/src/lib/public-site.ts +49 -0
- package/assets/templates/next-tenant-app/src/lib/theme-config.ts +10 -0
- package/assets/templates/next-tenant-app/src/lib/theme.tsx +159 -0
- package/assets/templates/next-tenant-app/src/lib/utils.ts +6 -0
- package/assets/templates/next-tenant-app/template.json +27 -0
- package/assets/templates/next-tenant-app/template.schema.json +160 -0
- package/assets/templates/next-tenant-app/test/server-only-stub.ts +1 -0
- package/assets/templates/next-tenant-app/tools/design-system/build-token-manifest.mjs +3 -0
- package/assets/templates/next-tenant-app/tools/design-system/check-imports.mjs +9 -0
- package/assets/templates/next-tenant-app/tools/design-system/check-stories.mjs +9 -0
- package/assets/templates/next-tenant-app/tools/design-system/check-values.mjs +9 -0
- package/assets/templates/next-tenant-app/tools/design-system/checks.mjs +238 -0
- package/assets/templates/next-tenant-app/tools/design-system/eslint-plugin-design-system.mjs +184 -0
- package/assets/templates/next-tenant-app/tools/design-system/playwright.config.mjs +34 -0
- package/assets/templates/next-tenant-app/tools/design-system/run-checks.mjs +22 -0
- package/assets/templates/next-tenant-app/tools/design-system/shared.mjs +166 -0
- package/assets/templates/next-tenant-app/tools/design-system/visual.spec.ts +41 -0
- package/assets/templates/next-tenant-app/tools/template/validate-route-contract.mjs +39 -0
- package/assets/templates/next-tenant-app/tools/template/validate-template.mjs +45 -0
- package/assets/templates/next-tenant-app/tsconfig.json +42 -0
- package/assets/templates/next-tenant-app/vitest.config.ts +25 -0
- package/bin/minutework.js +40 -0
- package/dist/auth.d.ts +59 -0
- package/dist/auth.js +338 -0
- package/dist/auth.js.map +1 -0
- package/dist/browser.d.ts +1 -0
- package/dist/browser.js +26 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +5 -0
- package/dist/cli.js.map +1 -0
- package/dist/compile.d.ts +20 -0
- package/dist/compile.js +121 -0
- package/dist/compile.js.map +1 -0
- package/dist/config.d.ts +25 -0
- package/dist/config.js +102 -0
- package/dist/config.js.map +1 -0
- package/dist/deploy-state.d.ts +35 -0
- package/dist/deploy-state.js +30 -0
- package/dist/deploy-state.js.map +1 -0
- package/dist/deploy.d.ts +22 -0
- package/dist/deploy.js +308 -0
- package/dist/deploy.js.map +1 -0
- package/dist/developer-client.d.ts +88 -0
- package/dist/developer-client.js +78 -0
- package/dist/developer-client.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +290 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +22 -0
- package/dist/init.js +421 -0
- package/dist/init.js.map +1 -0
- package/dist/launcher.d.ts +1 -0
- package/dist/launcher.js +50 -0
- package/dist/launcher.js.map +1 -0
- package/dist/paths.d.ts +12 -0
- package/dist/paths.js +33 -0
- package/dist/paths.js.map +1 -0
- package/dist/sandbox.d.ts +30 -0
- package/dist/sandbox.js +852 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/state.d.ts +46 -0
- package/dist/state.js +82 -0
- package/dist/state.js.map +1 -0
- package/dist/tokens.d.ts +14 -0
- package/dist/tokens.js +293 -0
- package/dist/tokens.js.map +1 -0
- package/package.json +43 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from fastapi.testclient import TestClient
|
|
4
|
+
|
|
5
|
+
from fastapi_sidecar.main import app
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_healthz_succeeds(configured_env) -> None:
|
|
9
|
+
with TestClient(app) as client:
|
|
10
|
+
response = client.get("/healthz")
|
|
11
|
+
|
|
12
|
+
assert response.status_code == 200
|
|
13
|
+
assert response.json() == {"status": "ok"}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_internal_route_rejects_missing_auth(configured_env) -> None:
|
|
17
|
+
with TestClient(app) as client:
|
|
18
|
+
response = client.post("/internal/v1/dispatch", json={"operation": "sync"})
|
|
19
|
+
|
|
20
|
+
assert response.status_code == 401
|
|
21
|
+
assert response.json()["detail"] == "Platform dispatch authentication required."
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_internal_route_accepts_platform_dispatch_auth(configured_env) -> None:
|
|
25
|
+
with TestClient(app) as client:
|
|
26
|
+
response = client.post(
|
|
27
|
+
"/internal/v1/dispatch",
|
|
28
|
+
json={"operation": "sync"},
|
|
29
|
+
headers={
|
|
30
|
+
"X-Platform-Dispatch-Token": configured_env["MW_PLATFORM_DISPATCH_TOKEN"],
|
|
31
|
+
"X-Request-Id": "req-test-123",
|
|
32
|
+
},
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
assert response.status_code == 200
|
|
36
|
+
payload = response.json()
|
|
37
|
+
assert payload["accepted"] is True
|
|
38
|
+
assert payload["receipt"]["request_id"] == "req-test-123"
|
|
39
|
+
assert payload["receipt"]["auth_mode"] == "internal_only"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from starlette.requests import Request
|
|
4
|
+
|
|
5
|
+
from fastapi_sidecar.auth import require_runtime_token
|
|
6
|
+
from fastapi_sidecar.settings import get_settings
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _build_request(headers: dict[str, str]) -> Request:
|
|
10
|
+
scope = {
|
|
11
|
+
"type": "http",
|
|
12
|
+
"headers": [
|
|
13
|
+
(key.lower().encode("utf-8"), value.encode("utf-8")) for key, value in headers.items()
|
|
14
|
+
],
|
|
15
|
+
}
|
|
16
|
+
return Request(scope)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_require_runtime_token_accepts_runtime_machine_headers(configured_env) -> None:
|
|
20
|
+
request = _build_request(
|
|
21
|
+
{
|
|
22
|
+
"X-Runtime-Id": configured_env["MW_RUNTIME_ID"],
|
|
23
|
+
"X-Runtime-Key": "runtime-secret",
|
|
24
|
+
"X-Request-Id": "req-runtime-token",
|
|
25
|
+
}
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
context = require_runtime_token(request, get_settings())
|
|
29
|
+
|
|
30
|
+
assert context.request_id == "req-runtime-token"
|
|
31
|
+
assert context.auth_mode == "runtime_token"
|
|
32
|
+
assert context.principal_kind == "runtime_machine"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
|
|
5
|
+
from fastapi_sidecar.bridge.client import BridgeClient
|
|
6
|
+
from fastapi_sidecar.settings import get_settings
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_bridge_client_builds_runtime_headers(configured_env) -> None:
|
|
10
|
+
client = BridgeClient(settings=get_settings(), client=httpx.Client(base_url="https://unused.test"))
|
|
11
|
+
|
|
12
|
+
assert client.runtime_headers() == {
|
|
13
|
+
"X-Runtime-Id": configured_env["MW_RUNTIME_ID"],
|
|
14
|
+
"X-Runtime-Key": "runtime-secret",
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_bridge_client_sends_runtime_headers(configured_env) -> None:
|
|
19
|
+
def handler(request: httpx.Request) -> httpx.Response:
|
|
20
|
+
assert request.headers["X-Runtime-Id"] == configured_env["MW_RUNTIME_ID"]
|
|
21
|
+
assert request.headers["X-Runtime-Key"] == "runtime-secret"
|
|
22
|
+
return httpx.Response(200, json={"ok": True})
|
|
23
|
+
|
|
24
|
+
transport = httpx.MockTransport(handler)
|
|
25
|
+
http_client = httpx.Client(
|
|
26
|
+
base_url=configured_env["MW_API_BASE_URL"],
|
|
27
|
+
transport=transport,
|
|
28
|
+
)
|
|
29
|
+
client = BridgeClient(settings=get_settings(), client=http_client)
|
|
30
|
+
|
|
31
|
+
assert client.request_json("POST", "/api/v1/test/", payload={"hello": "world"}) == {"ok": True}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from conftest import TEMPLATE_ROOT
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_materialization_smoke(configured_env, tmp_path) -> None:
|
|
12
|
+
destination = tmp_path / "app"
|
|
13
|
+
shutil.copytree(TEMPLATE_ROOT, destination)
|
|
14
|
+
|
|
15
|
+
for required_path in (
|
|
16
|
+
destination / "template.json",
|
|
17
|
+
destination / "template.schema.json",
|
|
18
|
+
destination / "pyproject.toml",
|
|
19
|
+
destination / "src" / "fastapi_sidecar" / "main.py",
|
|
20
|
+
destination / "src" / "fastapi_sidecar" / "worker.py",
|
|
21
|
+
):
|
|
22
|
+
assert required_path.exists()
|
|
23
|
+
|
|
24
|
+
env = os.environ.copy()
|
|
25
|
+
env.update(configured_env)
|
|
26
|
+
env["PYTHONPATH"] = str(destination / "src")
|
|
27
|
+
|
|
28
|
+
validator = subprocess.run(
|
|
29
|
+
[sys.executable, "tools/template/validate_template.py"],
|
|
30
|
+
cwd=destination,
|
|
31
|
+
env=env,
|
|
32
|
+
capture_output=True,
|
|
33
|
+
text=True,
|
|
34
|
+
check=False,
|
|
35
|
+
)
|
|
36
|
+
assert validator.returncode == 0, validator.stderr
|
|
37
|
+
|
|
38
|
+
import_smoke = subprocess.run(
|
|
39
|
+
[
|
|
40
|
+
sys.executable,
|
|
41
|
+
"-c",
|
|
42
|
+
(
|
|
43
|
+
"from fastapi_sidecar.main import app; "
|
|
44
|
+
"from fastapi_sidecar.worker import main; "
|
|
45
|
+
"assert app is not None; "
|
|
46
|
+
"raise SystemExit(main())"
|
|
47
|
+
),
|
|
48
|
+
],
|
|
49
|
+
cwd=destination,
|
|
50
|
+
env=env,
|
|
51
|
+
capture_output=True,
|
|
52
|
+
text=True,
|
|
53
|
+
check=False,
|
|
54
|
+
)
|
|
55
|
+
assert import_smoke.returncode == 0, import_smoke.stderr
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from conftest import NEXT_TEMPLATE_ROOT, SHARED_SCHEMA_PATH, TEMPLATE_ROOT
|
|
6
|
+
|
|
7
|
+
from fastapi_sidecar.template_validation import validate_template_manifest
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _load_json(path):
|
|
11
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_schema_copies_stay_identical() -> None:
|
|
15
|
+
shared = SHARED_SCHEMA_PATH.read_text(encoding="utf-8")
|
|
16
|
+
next_copy = (NEXT_TEMPLATE_ROOT / "template.schema.json").read_text(encoding="utf-8")
|
|
17
|
+
fastapi_copy = (TEMPLATE_ROOT / "template.schema.json").read_text(encoding="utf-8")
|
|
18
|
+
|
|
19
|
+
assert shared == next_copy == fastapi_copy
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_next_manifest_validates_against_bundled_schema() -> None:
|
|
23
|
+
validate_template_manifest(NEXT_TEMPLATE_ROOT)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_fastapi_manifest_validates_against_bundled_schema() -> None:
|
|
27
|
+
validate_template_manifest(TEMPLATE_ROOT)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_fastapi_manifest_declares_expected_processes() -> None:
|
|
31
|
+
manifest = _load_json(TEMPLATE_ROOT / "template.json")
|
|
32
|
+
assert manifest["template_profile"] == "bridge_internal"
|
|
33
|
+
assert manifest["required_bootstrap_steps"] == ["python_env_sync", "python_import_smoke"]
|
|
34
|
+
assert manifest["example_features"] == {}
|
|
35
|
+
assert manifest["sidecar_processes"] == [
|
|
36
|
+
{
|
|
37
|
+
"name": "api",
|
|
38
|
+
"process_type": "fastapi",
|
|
39
|
+
"entrypoint": "fastapi_sidecar.main:app",
|
|
40
|
+
"health_check_path": "/healthz",
|
|
41
|
+
"composition_profiles": ["internal_api_only", "worker_plus_internal_api"],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"name": "worker",
|
|
45
|
+
"process_type": "worker",
|
|
46
|
+
"entrypoint": "fastapi_sidecar.worker:main",
|
|
47
|
+
"composition_profiles": ["worker_only", "worker_plus_internal_api"],
|
|
48
|
+
},
|
|
49
|
+
]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
TEMPLATE_ROOT = Path(__file__).resolve().parents[2]
|
|
7
|
+
SRC_ROOT = TEMPLATE_ROOT / "src"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def run() -> int:
|
|
11
|
+
if str(SRC_ROOT) not in sys.path:
|
|
12
|
+
sys.path.insert(0, str(SRC_ROOT))
|
|
13
|
+
|
|
14
|
+
from fastapi_sidecar.template_validation import main
|
|
15
|
+
|
|
16
|
+
return main()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
if __name__ == "__main__":
|
|
20
|
+
raise SystemExit(run())
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
MW_PLATFORM_BASE_URL=http://127.0.0.1:8000
|
|
2
|
+
MW_CONTENT_API_TOKEN=
|
|
3
|
+
MW_TEMPLATE_APP_NAME=MinuteWork Combined Starter
|
|
4
|
+
# Local development example. Set this explicitly per environment.
|
|
5
|
+
MW_PUBLIC_BASE_URL=http://127.0.0.1:3000
|
|
6
|
+
MW_PUBLIC_SITE_PROPERTY_KEY=main-site
|
|
7
|
+
MW_PUBLIC_SITE_ENV=preview
|
|
8
|
+
MW_ENABLE_RUNTIME_COMMAND_EXAMPLE=false
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineMain } from "@storybook/nextjs-vite/node";
|
|
2
|
+
|
|
3
|
+
export default defineMain({
|
|
4
|
+
stories: [
|
|
5
|
+
"../src/design-system/**/*.stories.@(ts|tsx|mdx)",
|
|
6
|
+
"../src/design-system/docs/**/*.mdx",
|
|
7
|
+
],
|
|
8
|
+
addons: ["@storybook/addon-docs", "@storybook/addon-a11y"],
|
|
9
|
+
framework: {
|
|
10
|
+
name: "@storybook/nextjs-vite",
|
|
11
|
+
options: {
|
|
12
|
+
nextConfigPath: "../next.config.ts",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
staticDirs: ["../public"],
|
|
16
|
+
docs: {
|
|
17
|
+
defaultName: "Overview",
|
|
18
|
+
},
|
|
19
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { definePreview } from "@storybook/nextjs-vite";
|
|
2
|
+
|
|
3
|
+
import "../src/app/globals.css";
|
|
4
|
+
import { ThemeProvider } from "../src/lib/theme";
|
|
5
|
+
|
|
6
|
+
export default definePreview({
|
|
7
|
+
decorators: [
|
|
8
|
+
(Story) => (
|
|
9
|
+
<ThemeProvider
|
|
10
|
+
attribute="class"
|
|
11
|
+
defaultTheme="light"
|
|
12
|
+
enableSystem
|
|
13
|
+
disableTransitionOnChange
|
|
14
|
+
>
|
|
15
|
+
<div className="min-h-screen bg-background p-6 text-foreground">
|
|
16
|
+
<Story />
|
|
17
|
+
</div>
|
|
18
|
+
</ThemeProvider>
|
|
19
|
+
),
|
|
20
|
+
],
|
|
21
|
+
parameters: {
|
|
22
|
+
layout: "centered",
|
|
23
|
+
nextjs: {
|
|
24
|
+
appDirectory: true,
|
|
25
|
+
},
|
|
26
|
+
controls: {
|
|
27
|
+
matchers: {
|
|
28
|
+
color: /(background|color)$/i,
|
|
29
|
+
date: /Date$/i,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
options: {
|
|
33
|
+
storySort: {
|
|
34
|
+
order: ["Design System"],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Next Tenant App Template
|
|
2
|
+
|
|
3
|
+
This directory is the canonical combined Next.js web starter for Builder. It is the bundle that Builder will later materialize into `BuilderWorkspace.sandbox_root/app/`.
|
|
4
|
+
|
|
5
|
+
`apps/mw-next-client` is still the repo-side seed used to harden and evolve this scaffold. It is not the runtime template location.
|
|
6
|
+
|
|
7
|
+
## Auth profile
|
|
8
|
+
|
|
9
|
+
This template stays fixed to the `platform_session_bff` profile:
|
|
10
|
+
|
|
11
|
+
- the browser talks only to the Next.js app
|
|
12
|
+
- upstream platform `sessionid` and `csrftoken` stay server-owned
|
|
13
|
+
- unsafe upstream requests bootstrap and forward CSRF through the BFF
|
|
14
|
+
- there is no browser-to-runtime direct access
|
|
15
|
+
- there is no local JWT system, local user table, or parallel auth stack
|
|
16
|
+
|
|
17
|
+
## Runtime requirements
|
|
18
|
+
|
|
19
|
+
- Node.js `20.9.0` or newer
|
|
20
|
+
- `pnpm@9.0.0`
|
|
21
|
+
|
|
22
|
+
## Default route shape
|
|
23
|
+
|
|
24
|
+
The template ships one combined app with two explicit route surfaces:
|
|
25
|
+
|
|
26
|
+
- public routes at the root: `/`, `/pricing`, `/docs`, `/blog`, `/login`
|
|
27
|
+
- authenticated routes under `/app`
|
|
28
|
+
- optional runtime example under `/app/examples/runtime-commands`
|
|
29
|
+
|
|
30
|
+
Public marketing, docs, and blog routes render through a swappable public-content adapter seam. The authenticated shell keeps tenant context controls, password flows, and the platform-session BFF boundary.
|
|
31
|
+
|
|
32
|
+
## Public content adapter
|
|
33
|
+
|
|
34
|
+
The public content seam lives under `src/lib/content`.
|
|
35
|
+
|
|
36
|
+
The default built-in adapter is `MinuteWorkPublicSiteContentAdapter`. It calls the
|
|
37
|
+
MinuteWork gateway with a server-only content token and reads the narrow
|
|
38
|
+
`mw.core.site` public-site snapshot contract.
|
|
39
|
+
|
|
40
|
+
The gateway response carries an explicit boundary:
|
|
41
|
+
|
|
42
|
+
- `environment=preview` must declare `source_boundary=runtime_preview`
|
|
43
|
+
- `environment=live` must declare `source_boundary=published_live`
|
|
44
|
+
|
|
45
|
+
That keeps preview free to use runtime-backed draft reads while keeping live
|
|
46
|
+
publication-safe for static generation and anonymous delivery.
|
|
47
|
+
|
|
48
|
+
To replace the built-ins for a tenant-specific CMS or content API, implement the hook in `src/lib/content/custom-adapter.ts`.
|
|
49
|
+
|
|
50
|
+
## Example gating
|
|
51
|
+
|
|
52
|
+
`MW_ENABLE_RUNTIME_COMMAND_EXAMPLE=false` by default.
|
|
53
|
+
|
|
54
|
+
When disabled:
|
|
55
|
+
|
|
56
|
+
- `/app/examples/runtime-commands` returns `404`
|
|
57
|
+
- the example gateway API routes return `404`
|
|
58
|
+
- the example navigation entry is omitted
|
|
59
|
+
|
|
60
|
+
## Environment
|
|
61
|
+
|
|
62
|
+
Copy `.env.example` to `.env.local` when running the template directly.
|
|
63
|
+
|
|
64
|
+
- `MW_PLATFORM_BASE_URL` is required
|
|
65
|
+
- `MW_CONTENT_API_TOKEN` is required and must stay server-only
|
|
66
|
+
- `MW_TEMPLATE_APP_NAME` defaults to `MinuteWork Combined Starter`
|
|
67
|
+
- `MW_PUBLIC_BASE_URL` is required and should match the deployed public origin
|
|
68
|
+
- `MW_PUBLIC_SITE_PROPERTY_KEY` is required and selects the `PublishedWebProperty` backing the site
|
|
69
|
+
- `MW_PUBLIC_SITE_ENV` defaults to `preview`
|
|
70
|
+
- `MW_ENABLE_RUNTIME_COMMAND_EXAMPLE` defaults to `false`
|
|
71
|
+
|
|
72
|
+
## SEO baseline
|
|
73
|
+
|
|
74
|
+
The public route surface ships with:
|
|
75
|
+
|
|
76
|
+
- canonical URLs derived from `MW_PUBLIC_BASE_URL`
|
|
77
|
+
- `robots.ts` and `sitemap.ts`
|
|
78
|
+
- Open Graph metadata for marketing and article/detail pages
|
|
79
|
+
- server-rendered public pages by default, with no tenant/session reads during render
|
|
80
|
+
- docs/blog static params generated from the same public-site snapshot used during render
|
|
81
|
+
- graceful empty states for fresh properties with no authored or no published content
|
|
82
|
+
|
|
83
|
+
Private routes under `/app` are marked `noindex`.
|
|
84
|
+
|
|
85
|
+
## Generated artifact policy
|
|
86
|
+
|
|
87
|
+
Checked-in generated artifacts are limited to:
|
|
88
|
+
|
|
89
|
+
- `next-env.d.ts`
|
|
90
|
+
- `src/design-system/tokens/manifest.ts`
|
|
91
|
+
- `src/design-system/tokens/manifest.json`
|
|
92
|
+
- `pnpm-lock.yaml`
|
|
93
|
+
|
|
94
|
+
Generated build outputs such as `.next/`, `storybook-static/`, `test-results/`, and `tools/design-system/__screenshots__/` are not canonical template contents.
|
|
95
|
+
|
|
96
|
+
## Validation
|
|
97
|
+
|
|
98
|
+
Use `pnpm validate` from this directory. It runs:
|
|
99
|
+
|
|
100
|
+
1. `template:validate`
|
|
101
|
+
2. `template:route-contract`
|
|
102
|
+
3. `next typegen`
|
|
103
|
+
4. `design-system:tokens`
|
|
104
|
+
5. `design-system:check`
|
|
105
|
+
6. `typecheck`
|
|
106
|
+
7. `lint`
|
|
107
|
+
8. `test`
|
|
108
|
+
9. `build`
|
|
109
|
+
10. `build-storybook`
|
|
110
|
+
|
|
111
|
+
`template:validate` validates `template.json` against the bundled `template.schema.json`, so the template remains self-validating after Builder materializes it into a workspace sandbox.
|
|
112
|
+
|
|
113
|
+
`template:route-contract` verifies that the combined public/private route tree, metadata routes, and content-adapter entrypoints remain present in the template.
|
|
114
|
+
|
|
115
|
+
Inside the repo, the shared schema at `runtime/builder/templates/template.schema.json` is authoritative. The bundled `template.schema.json` inside this template must stay byte-equivalent, and `template:validate` fails if the two drift.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "new-york",
|
|
4
|
+
"rsc": true,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "src/app/globals.css",
|
|
9
|
+
"baseColor": "neutral",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"aliases": {
|
|
14
|
+
"components": "@/design-system",
|
|
15
|
+
"utils": "@/lib/utils",
|
|
16
|
+
"ui": "@/design-system/primitives",
|
|
17
|
+
"lib": "@/lib",
|
|
18
|
+
"hooks": "@/hooks"
|
|
19
|
+
},
|
|
20
|
+
"iconLibrary": "lucide"
|
|
21
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import nextCoreWebVitals from "eslint-config-next/core-web-vitals";
|
|
2
|
+
import nextTypeScript from "eslint-config-next/typescript";
|
|
3
|
+
|
|
4
|
+
import designSystemPlugin from "./tools/design-system/eslint-plugin-design-system.mjs";
|
|
5
|
+
|
|
6
|
+
const eslintConfig = [
|
|
7
|
+
...nextCoreWebVitals,
|
|
8
|
+
...nextTypeScript,
|
|
9
|
+
{
|
|
10
|
+
ignores: [
|
|
11
|
+
".next/**",
|
|
12
|
+
"node_modules/**",
|
|
13
|
+
"next-env.d.ts",
|
|
14
|
+
"storybook-static/**",
|
|
15
|
+
"src/design-system/tokens/manifest.ts",
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
files: ["src/app/**/*.{ts,tsx}", "src/features/**/*.{ts,tsx}"],
|
|
20
|
+
plugins: {
|
|
21
|
+
"design-system": designSystemPlugin,
|
|
22
|
+
},
|
|
23
|
+
rules: {
|
|
24
|
+
"design-system/no-design-system-bypass-imports": "error",
|
|
25
|
+
"design-system/no-cva-outside-design-system": "error",
|
|
26
|
+
"design-system/no-raw-design-values": "error",
|
|
27
|
+
"design-system/no-bespoke-visual-stacks-in-features": "error",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
files: ["src/design-system/**/*.{ts,tsx}"],
|
|
32
|
+
plugins: {
|
|
33
|
+
"design-system": designSystemPlugin,
|
|
34
|
+
},
|
|
35
|
+
rules: {
|
|
36
|
+
"design-system/no-raw-design-values": "error",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
export default eslintConfig;
|