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

Commit 20525f8

Browse files
authored
Merge pull request #50 from publicodes/feat-cli
Introduce a CLI
2 parents e3bd88f + 9016817 commit 20525f8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+3558
-1662
lines changed

.gitattributes

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

.github/workflows/build.yml

+5
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ jobs:
1010
- uses: actions/setup-node@v3
1111
with:
1212
cache: yarn
13+
- uses: oven-sh/setup-bun@v2
14+
1315
- run: yarn install --immutable
1416

1517
- name: Build package
@@ -20,3 +22,6 @@ jobs:
2022

2123
- name: Check file formatting
2224
run: yarn format:check
25+
26+
- name: pkg.pr.new
27+
run: npx pkg-pr-new publish

bin/dev.cmd

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@echo off
2+
3+
node --loader ts-node/esm --no-warnings=ExperimentalWarning "%~dp0\dev" %*

bin/dev.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env bun
2+
3+
// eslint-disable-next-line n/shebang
4+
import { execute } from '@oclif/core'
5+
6+
await execute({ development: true, dir: import.meta.url })

bin/run.cmd

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@echo off
2+
3+
node "%~dp0\run" %*

bin/run.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env node
2+
3+
import { execute } from '@oclif/core'
4+
5+
await execute({ dir: import.meta.url })

package.json

+58-25
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
11
{
22
"name": "@publicodes/tools",
3-
"version": "1.2.5",
4-
"description": "A set of utility functions to build tools around Publicodes models",
3+
"version": "1.4.0",
4+
"description": "A CLI tool for Publicodes",
55
"type": "module",
6-
"main": "dist/index.js",
7-
"scripts": {
8-
"build": "tsup",
9-
"watch": "tsup --watch",
10-
"clean": "rm -rf dist docs",
11-
"test": "jest",
12-
"docs": "typedoc",
13-
"format": "prettier --write .",
14-
"format:check": "prettier --check ."
15-
},
16-
"engines": {
17-
"node": ">=17"
6+
"main": "publicodes-build/index.js",
7+
"bin": {
8+
"publicodes": "./bin/run.js"
189
},
10+
"files": [
11+
"publicodes-build",
12+
"dist",
13+
"bin",
14+
"quick-doc"
15+
],
1916
"exports": {
2017
".": {
2118
"import": "./dist/index.js",
@@ -38,9 +35,6 @@
3835
"types": "./dist/migration/index.d.ts"
3936
}
4037
},
41-
"files": [
42-
"dist"
43-
],
4438
"repository": {
4539
"type": "git",
4640
"url": "git+ssh://[email protected]/publicodes/tools.git"
@@ -55,31 +49,59 @@
5549
],
5650
"author": "Emile Rolley <[email protected]>",
5751
"license": "MIT",
52+
"scripts": {
53+
"build": "tsup",
54+
"watch": "tsup --watch",
55+
"clean": "rm -rf dist docs",
56+
"test": "vitest run --globals",
57+
"docs": "typedoc",
58+
"format": "prettier --write .",
59+
"format:check": "prettier --check .",
60+
"compile": "publicodes compile",
61+
"dev": "publicodes dev"
62+
},
63+
"engines": {
64+
"node": ">=17"
65+
},
5866
"dependencies": {
67+
"@clack/prompts": "^0.7.0",
68+
"@oclif/core": "^4.0.23",
69+
"@publicodes/react-ui": "^1.5.4",
70+
"@tailwindcss/typography": "^0.5.16",
71+
"@tailwindcss/vite": "^4.0.0",
5972
"@types/node": "^18.11.18",
73+
"chalk": "^5.3.0",
74+
"chokidar": "^4.0.3",
6075
"glob": "^10.4.1",
6176
"path": "^0.12.7",
62-
"publicodes": "^1.3.3",
63-
"yaml": "^2.4.5"
77+
"publicodes": "^1.6.1",
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"
6484
},
6585
"devDependencies": {
66-
"@types/jest": "^29.2.5",
86+
"@oclif/test": "^4.0.9",
87+
"@types/jest": "^29.5.13",
88+
"@types/react": "^19.0.8",
6789
"docdash": "^2.0.1",
68-
"jest": "^29.4.1",
69-
"prettier": "^3.0.0",
70-
"ts-jest": "^29.0.4",
90+
"prettier": "^3.4.2",
7191
"ts-node": "^10.9.2",
7292
"tsup": "^8.0.2",
7393
"typedoc": "^0.24.8",
7494
"typedoc-plugin-export-functions": "^1.0.0",
75-
"typescript": "^4.9.4"
95+
"typescript": "^4.9.4",
96+
"vitest": "^2.1.2"
7697
},
7798
"tsup": {
7899
"entry": [
79100
"src/index.ts",
80101
"src/optims/index.ts",
81102
"src/compilation/index.ts",
82-
"src/migration/index.ts"
103+
"src/migration/index.ts",
104+
"src/commands"
83105
],
84106
"format": [
85107
"cjs",
@@ -90,7 +112,18 @@
90112
"clean": true,
91113
"cjsInterop": true
92114
},
115+
"oclif": {
116+
"bin": "publicodes",
117+
"commands": "./dist/commands",
118+
"dirname": "publicodes",
119+
"topicSeparator": ":"
120+
},
93121
"publishConfig": {
94122
"access": "public"
123+
},
124+
"packageManager": "[email protected]+sha1.4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447",
125+
"types": "publicodes-build/index.d.ts",
126+
"peerDependencies": {
127+
"publicodes": "^1.5.1"
95128
}
96129
}

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+
}

0 commit comments

Comments
 (0)