* feat(scripts): add MOBILE_FIRST_MARKERS + _check_mobile_first check * feat(templates): fullstack mobile-first PWA manifest + mobile Playwright + axe * test(scripts): cover mobile-first markers, check, cookiecutter structure * fix(ci): reformat test_project_status.py string literals --------- Co-authored-by: opencode-agent <agent@opencode.local>
95 lines
3.6 KiB
Python
95 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Project contract: single source of truth for project types, stacks,
|
|
expected structure, frontend markers.
|
|
|
|
Imported by both ``spec-status.py`` and ``project-status.py`` via
|
|
``importlib.util.spec_from_file_location`` (no ``sys.path`` mutation).
|
|
Stdlib-only imports.
|
|
|
|
Contract symbols:
|
|
ProjectType — StrEnum with 7 members (incl. MCP_SERVER + UNKNOWN)
|
|
VALID_TYPES — set[str] excluding "unknown"
|
|
STACK_REQUIRED — dict[type -> list[str]] of mandatory stack items
|
|
STRUCTURE_EXPECTED — dict[type -> list[str]] of expected top-level dirs/files
|
|
FRONTEND_STACK_MARKERS — dict with keys for fullstack frontend detection
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from enum import StrEnum
|
|
|
|
|
|
class ProjectType(StrEnum):
|
|
"""Project type enum (auto-detected or spec-declared)."""
|
|
|
|
FULLSTACK = "fullstack"
|
|
BACKEND = "backend"
|
|
CLI = "cli"
|
|
BOT = "bot"
|
|
WORKER = "worker"
|
|
MCP_SERVER = "mcp-server"
|
|
UNKNOWN = "unknown"
|
|
|
|
|
|
# Excludes "unknown" — used by spec-status PROJECT_TYPE phase validation.
|
|
VALID_TYPES: set[str] = {t.value for t in ProjectType if t != ProjectType.UNKNOWN}
|
|
|
|
# Mandatory stack items per project type (spec-status Phase 2 STACK).
|
|
# `uv` stays in ALL stacks (build-tool, mentioned in stack.md).
|
|
STACK_REQUIRED: dict[str, list[str]] = {
|
|
"backend": ["fastapi", "tortoise", "uv", "pytest", "ruff", "mypy", "loguru", "pydantic"],
|
|
"fullstack": [
|
|
"fastapi",
|
|
"tortoise",
|
|
"svelte",
|
|
"sveltekit",
|
|
"biome",
|
|
"uv",
|
|
"ruff",
|
|
"mypy",
|
|
"pytest",
|
|
"tailwind",
|
|
"shadcn",
|
|
"typescript",
|
|
],
|
|
"mcp-server": ["fastapi", "mcp", "patchright", "uv"],
|
|
"cli": ["typer", "uv", "hatchling", "ruff", "mypy", "pytest"],
|
|
"bot": ["aiogram", "fastapi", "uv", "ruff", "mypy", "pytest"],
|
|
"worker": ["prefect", "uv", "ruff", "mypy", "pytest"],
|
|
}
|
|
|
|
# Expected top-level structure per type (project-status check_structure).
|
|
# BACKEND is resolved dynamically by ``_expected_backend_paths`` (src/<pkg>/...),
|
|
# so it is NOT in this dict. mcp-server structure is TBD out of scope.
|
|
# Keys are strings (project type values), NOT ProjectType enum members —
|
|
# kept as plain strings for portability across both oracles.
|
|
STRUCTURE_EXPECTED: dict[str, list[str]] = {
|
|
"fullstack": ["backend", "frontend"],
|
|
"cli": ["src"], # src/<package>/ — checked generically
|
|
"bot": ["src/bot.py"],
|
|
"worker": ["src/flow.py"],
|
|
"unknown": [],
|
|
}
|
|
|
|
# Fullstack frontend stack markers (project-status _check_type_specific_structure).
|
|
# `tailwindcss` + `bits-ui` (shadcn-svelte proxy) in package.json deps, plus
|
|
# `components.json` (shadcn config) and `tsconfig.json` (TypeScript) existence.
|
|
FRONTEND_STACK_MARKERS: dict[str, list[str]] = {
|
|
"fullstack_package_deps": ["tailwindcss", "bits-ui"],
|
|
"fullstack_files": ["frontend/components.json", "frontend/tsconfig.json"],
|
|
}
|
|
|
|
# Fullstack mobile-first markers (project-status _check_mobile_first, issue #278).
|
|
# Distinct from FRONTEND_STACK_MARKERS so the stack check stays focused on the
|
|
# Tailwind/shadcn/TS trio. Keys:
|
|
# fullstack_files — paths (relative to repo root) that must exist
|
|
# for PWA + mobile Playwright + a11y to be present.
|
|
# fullstack_app_html_markers — substrings that must appear in app.html <head>.
|
|
MOBILE_FIRST_MARKERS: dict[str, list[str]] = {
|
|
"fullstack_files": [
|
|
"frontend/static/manifest.webmanifest",
|
|
"frontend/tests/e2e/mobile.spec.ts",
|
|
"frontend/tests/e2e/accessibility.spec.ts",
|
|
],
|
|
"fullstack_app_html_markers": ["viewport", "manifest"],
|
|
}
|