Skip to content
This repository was archived by the owner on Feb 14, 2025. It is now read-only.

Commit e2e9421

Browse files
committed
Merge pull request #51 from publicodes/feat-cli-dev
feat: add a 'publicodes dev' command
2 parents 86bbf76 + a9b6ced commit e2e9421

25 files changed

+1697
-176
lines changed

.gitattributes

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.publicodes linguist-language=YAML

package.json

+28-9
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
{
22
"name": "@publicodes/tools",
3-
"version": "1.3.0-2",
3+
"version": "1.4.0",
44
"description": "A CLI tool for Publicodes",
55
"type": "module",
6-
"main": "dist/index.js",
6+
"main": "publicodes-build/index.js",
77
"bin": {
88
"publicodes": "./bin/run.js"
99
},
1010
"files": [
11+
"publicodes-build",
1112
"dist",
12-
"bin"
13+
"bin",
14+
"quick-doc"
1315
],
1416
"exports": {
1517
".": {
@@ -51,35 +53,47 @@
5153
"build": "tsup",
5254
"watch": "tsup --watch",
5355
"clean": "rm -rf dist docs",
54-
"test": "vitest --globals",
56+
"test": "vitest run --globals",
5557
"docs": "typedoc",
5658
"format": "prettier --write .",
57-
"format:check": "prettier --check ."
59+
"format:check": "prettier --check .",
60+
"compile": "publicodes compile",
61+
"dev": "publicodes dev"
5862
},
5963
"engines": {
6064
"node": ">=17"
6165
},
6266
"dependencies": {
6367
"@clack/prompts": "^0.7.0",
6468
"@oclif/core": "^4.0.23",
69+
"@publicodes/react-ui": "^1.5.4",
70+
"@tailwindcss/typography": "^0.5.16",
71+
"@tailwindcss/vite": "^4.0.0",
6572
"@types/node": "^18.11.18",
6673
"chalk": "^5.3.0",
74+
"chokidar": "^4.0.3",
6775
"glob": "^10.4.1",
6876
"path": "^0.12.7",
6977
"publicodes": "^1.6.1",
70-
"yaml": "^2.4.5"
78+
"react": "^19.0.0",
79+
"react-dom": "^19.0.0",
80+
"react-router-dom": "^7.1.3",
81+
"tailwindcss": "^4.0.0",
82+
"vite": "^6.0.11",
83+
"yaml": "^2.7.0"
7184
},
7285
"devDependencies": {
7386
"@oclif/test": "^4.0.9",
7487
"@types/jest": "^29.5.13",
88+
"@types/react": "^19.0.8",
7589
"docdash": "^2.0.1",
76-
"prettier": "^3.0.0",
90+
"prettier": "^3.4.2",
7791
"ts-node": "^10.9.2",
7892
"tsup": "^8.0.2",
7993
"typedoc": "^0.24.8",
8094
"typedoc-plugin-export-functions": "^1.0.0",
8195
"typescript": "^4.9.4",
82-
"vitest": "^2.1.1"
96+
"vitest": "^2.1.2"
8397
},
8498
"tsup": {
8599
"entry": [
@@ -106,5 +120,10 @@
106120
},
107121
"publishConfig": {
108122
"access": "public"
123+
},
124+
"packageManager": "[email protected]+sha1.4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447",
125+
"types": "publicodes-build/index.d.ts",
126+
"peerDependencies": {
127+
"publicodes": "^1.5.1"
109128
}
110-
}
129+
}

quick-doc/README.md

Whitespace-only changes.

quick-doc/index.html

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>⚡️ QuickDoc - Publicodes</title>
7+
<!-- <link rel="stylesheet" href="./src/index.css" /> -->
8+
</head>
9+
<body>
10+
<div id="root"></div>
11+
12+
<script type="module" src="/src/index.tsx"></script>
13+
<link href="/src/app.css" rel="stylesheet" />
14+
</body>
15+
</html>

quick-doc/src/app.css

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
@import 'tailwindcss';
2+
3+
@layer base {
4+
h1 {
5+
@apply text-4xl font-bold mt-8 mb-6 text-[#10162F];
6+
}
7+
8+
h2 {
9+
@apply text-3xl font-semibold mt-6 mb-5 text-[#10162F];
10+
}
11+
12+
h3 {
13+
@apply text-2xl font-semibold mt-5 mb-4 text-[#10162F];
14+
}
15+
16+
p {
17+
@apply text-base leading-relaxed mb-4 text-[#4B5563];
18+
}
19+
20+
ul {
21+
@apply list-disc list-inside mb-4 space-y-2;
22+
}
23+
24+
ol {
25+
@apply list-decimal list-inside mb-4 space-y-2;
26+
}
27+
28+
li {
29+
@apply text-[#4B5563] leading-relaxed;
30+
}
31+
32+
a {
33+
@apply text-[#2975d1] hover:text-[#1a365d] transition-colors underline-offset-2 hover:underline;
34+
}
35+
36+
blockquote {
37+
@apply pl-4 border-l-4 border-[#E5E7EB] italic my-4;
38+
}
39+
40+
code {
41+
@apply bg-[#F3F4F6] px-2 py-1 rounded text-sm text-[#10162F];
42+
}
43+
44+
pre {
45+
@apply bg-[#F3F4F6] p-4 rounded-lg mb-4 overflow-x-auto;
46+
}
47+
button {
48+
@apply bg-slate-100 px-2 py-1 rounded-md hover:bg-slate-50 transition-colors cursor-pointer disabled:opacity-50 text-sm disabled:cursor-not-allowed border border-slate-600 text-slate-800 ml-2;
49+
}
50+
}
51+
52+
@keyframes fade-in {
53+
from {
54+
opacity: 0;
55+
}
56+
to {
57+
opacity: 1;
58+
}
59+
}
60+
61+
.animate-fade-in {
62+
animation: fade-in 0.15s ease-out;
63+
}

quick-doc/src/components/App.tsx

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { RulePage } from '@publicodes/react-ui'
2+
import { useEffect, useState } from 'react'
3+
import {
4+
BrowserRouter,
5+
Link,
6+
Navigate,
7+
Route,
8+
Routes,
9+
useParams,
10+
} from 'react-router-dom'
11+
import { engine, onEngineUpdate } from '../engine'
12+
import { sitemap } from '../sitemap'
13+
import { onSituationUpdate, situations } from '../situations'
14+
import Header from './Header'
15+
import { Error } from './Error'
16+
17+
function RulePageWrapper() {
18+
let { '*': splat } = useParams()
19+
return (
20+
<RulePage
21+
engine={engine}
22+
documentationPath=""
23+
rulePath={splat}
24+
searchBar={true}
25+
showDevSection={true}
26+
language="fr"
27+
renderers={{ Link: Link }}
28+
/>
29+
)
30+
}
31+
32+
export default function App() {
33+
const [, forceUpdate] = useState({})
34+
useEffect(() => {
35+
// Subscribe to engine updates
36+
return onEngineUpdate(() => forceUpdate({}))
37+
}, [])
38+
const [activeSituation, setActiveSituation] = useState('')
39+
40+
useEffect(() => {
41+
return onSituationUpdate(() => {
42+
engine.setSituation(situations[activeSituation] ?? {})
43+
setActiveSituation(activeSituation in situations ? activeSituation : '')
44+
forceUpdate({})
45+
})
46+
}, [activeSituation])
47+
48+
function handleSituationChange(situation: string) {
49+
setActiveSituation(situation)
50+
engine.setSituation(situations[situation] ?? {})
51+
}
52+
53+
return (
54+
<>
55+
<BrowserRouter>
56+
<Header
57+
setSituation={handleSituationChange}
58+
activeSituation={activeSituation}
59+
/>
60+
<div className="container mx-auto px-4">
61+
<Error />
62+
<Routes>
63+
<Route
64+
path="/"
65+
element={<Navigate to={Object.keys(sitemap)[0]} replace />}
66+
/>
67+
<Route path="/*" element={<RulePageWrapper />} />
68+
</Routes>
69+
</div>
70+
</BrowserRouter>
71+
</>
72+
)
73+
}

quick-doc/src/components/Error.tsx

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import React from 'react'
2+
import { error } from '../engine'
3+
4+
export function Error() {
5+
if (error.length === 0) return null
6+
return (
7+
<>
8+
<div
9+
className="fixed z-[1000]
10+
inset-0 bg-slate-400/50 backdrop-blur-[2px] animate-fade-in "
11+
/>
12+
<div
13+
className="fixed z-[1000] left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-xl bg-white rounded-lg shadow-lg animate-slide-up"
14+
role="alert"
15+
>
16+
<div className="p-6">
17+
<div className="space-y-2 max-h-[70vh] overflow-auto">
18+
{error.map((err, index) => {
19+
const { type, message, ruleName } = parseError(err)
20+
return (
21+
<React.Fragment key={index}>
22+
<div className="flex items-center gap-3 ">
23+
<svg
24+
className="w-6 h-6 text-red-500 flex-shrink-0"
25+
fill="none"
26+
stroke="currentColor"
27+
viewBox="0 0 24 24"
28+
>
29+
<path
30+
strokeLinecap="round"
31+
strokeLinejoin="round"
32+
strokeWidth={2}
33+
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
34+
/>
35+
</svg>
36+
<h2 className="text-lg m-0 py-4 font-semibold text-red-500">
37+
{type}
38+
</h2>
39+
</div>
40+
<p className="text-xl font-semibold flex items-baseline">
41+
Dans la règle
42+
<code className="px-1 ml-2 text-slate-800 bg-gray-100 rounded-md">
43+
{ruleName}
44+
</code>
45+
</p>
46+
47+
<p key={index} className="text-gray-600 ">
48+
{message}
49+
</p>
50+
</React.Fragment>
51+
)
52+
})}
53+
</div>
54+
</div>
55+
</div>
56+
</>
57+
)
58+
}
59+
60+
/**
61+
*
62+
* @example
63+
* ```js
64+
* parseError(`[ Erreur syntaxique ] ➡️ Dans la règle "salaire net" ✖️ La référence "salaire brut" est introuvable. Vérifiez que l'orthographe et l'espace de nom sont corrects`)
65+
* ```
66+
*/
67+
function parseError(error: string): {
68+
type: string
69+
message: string
70+
ruleName: string
71+
} {
72+
const type = error.match(/\[ (.+?) \]/)?.[1] ?? ''
73+
const ruleName = error.match(/"(.+?)"/)?.[1] ?? ''
74+
const message = error.split('✖️')[1].trim()
75+
return { type, message, ruleName }
76+
}

quick-doc/src/components/Header.tsx

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { Link } from 'react-router-dom'
2+
import { situations } from '../situations'
3+
4+
export default function Header({
5+
setSituation,
6+
activeSituation,
7+
}: {
8+
setSituation: (situation: string) => void
9+
activeSituation: string
10+
}) {
11+
return (
12+
<header className=" container mx-auto p-4">
13+
<div className="flex items-center justify-between flex-col md:flex-row">
14+
<h1>
15+
<Link to="/" className="text-xl font-bold">
16+
⚡ Quick-doc
17+
</Link>
18+
</h1>
19+
20+
<nav className="w-full md:w-auto ">
21+
<div className="flex flex-col gap-2">
22+
<label
23+
htmlFor="situation-select"
24+
className="text-sm font-medium text-gray-700"
25+
>
26+
Selectionner une situation
27+
</label>
28+
<select
29+
id="situation-select"
30+
value={activeSituation}
31+
onChange={(e) => setSituation(e.target.value)}
32+
className="block w-full md:w-64 px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
33+
>
34+
<option value=""></option>
35+
{Object.keys(situations).map((situationName) => (
36+
<option key={situationName} value={situationName}>
37+
{situationName}
38+
</option>
39+
))}
40+
</select>
41+
</div>
42+
</nav>
43+
</div>
44+
</header>
45+
)
46+
}

quick-doc/src/components/QuickSearch.tsx

Whitespace-only changes.
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Link } from 'react-router-dom'
2+
import { sitemap } from '../sitemap'
3+
4+
export function RulesIndex() {
5+
return (
6+
<div>
7+
<h1>Index des règles</h1>
8+
<ul>
9+
{Object.entries(sitemap).map(([path, name]) => (
10+
<li key={path}>
11+
<Link to={path}>{name}</Link>
12+
</li>
13+
))}
14+
</ul>
15+
</div>
16+
)
17+
}

0 commit comments

Comments
 (0)