Compare commits
3 commits
86c2d71b5e
...
cb8fbb4b3b
| Author | SHA1 | Date | |
|---|---|---|---|
| cb8fbb4b3b | |||
| 7a77534d36 | |||
| e2d7207237 |
32 changed files with 3120 additions and 1 deletions
8
.changeset/README.md
Normal file
8
.changeset/README.md
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# Changesets
|
||||||
|
|
||||||
|
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
||||||
|
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
||||||
|
find the full documentation for it [in our repository](https://github.com/changesets/changesets).
|
||||||
|
|
||||||
|
We have a quick list of common questions to get you started engaging with this project in
|
||||||
|
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md).
|
||||||
11
.changeset/config.json
Normal file
11
.changeset/config.json
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://unpkg.com/@changesets/config@3.1.4/schema.json",
|
||||||
|
"changelog": "@changesets/cli/changelog",
|
||||||
|
"commit": false,
|
||||||
|
"fixed": [],
|
||||||
|
"linked": [],
|
||||||
|
"access": "restricted",
|
||||||
|
"baseBranch": "main",
|
||||||
|
"updateInternalDependencies": "patch",
|
||||||
|
"ignore": []
|
||||||
|
}
|
||||||
5
.changeset/migrate-web-component-to-monorepo.md
Normal file
5
.changeset/migrate-web-component-to-monorepo.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"@lightweight-sdk/web-component": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Migration de `web-component` dans le monorepo lightweight-sdk. Aucun changement de comportement : le code source est repris tel quel depuis l'ancien repo `lightweight-sdk-web-component`, seuls l'outillage (Biome, Changesets, publication sur le registre npm privé) et l'organisation du dépôt changent.
|
||||||
18
.editorconfig
Normal file
18
.editorconfig
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.{yml,yaml}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
trim_trailing_whitespace = false
|
||||||
51
.forgejo/workflows/ci.yml
Normal file
51
.forgejo/workflows/ci.yml
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-lint-test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "24"
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Get pnpm store directory
|
||||||
|
id: pnpm-cache
|
||||||
|
shell: bash
|
||||||
|
run: echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Cache pnpm store
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||||
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Lint (Biome)
|
||||||
|
run: pnpm lint
|
||||||
|
|
||||||
|
- name: Build (tsc -b)
|
||||||
|
run: pnpm build
|
||||||
|
|
||||||
|
- name: Test (Vitest)
|
||||||
|
run: pnpm test
|
||||||
|
|
||||||
|
- name: Docs check (TypeDoc validation)
|
||||||
|
run: pnpm docs:check
|
||||||
51
.forgejo/workflows/release.yml
Normal file
51
.forgejo/workflows/release.yml
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
# Publie les packages modifiés sur le registre npm privé Forgejo
|
||||||
|
# (https://git.dev.kyuno.fr/api/packages/kyuno/npm/) via Changesets.
|
||||||
|
#
|
||||||
|
# - déclenché sur push vers `main` (typiquement après le merge d'une PR
|
||||||
|
# "Version Packages" générée par `pnpm changeset version`)
|
||||||
|
# - `changeset publish` est idempotent par package : republie uniquement
|
||||||
|
# les packages dont la version locale n'existe pas encore sur le registre
|
||||||
|
# - prérequis côté instance : secret de repo REGISTRY_TOKEN, un jeton
|
||||||
|
# Forgejo avec les scopes write:package + read:package (Settings ->
|
||||||
|
# Applications) - le jeton automatique des Actions n'a pas ce droit
|
||||||
|
# sur cette instance (voir spec/ci-cd.md)
|
||||||
|
|
||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "24"
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: pnpm build
|
||||||
|
|
||||||
|
- name: Configure registry authentication
|
||||||
|
run: echo "//git.dev.kyuno.fr/api/packages/kyuno/npm/:_authToken=${REGISTRY_TOKEN}" >> .npmrc
|
||||||
|
env:
|
||||||
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||||
|
|
||||||
|
- name: Publish changed packages
|
||||||
|
run: pnpm changeset:publish
|
||||||
|
env:
|
||||||
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
* text=auto eol=lf
|
||||||
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
*.tsbuildinfo
|
||||||
|
docs/api/
|
||||||
|
coverage/
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
.vscode/
|
||||||
|
.env
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Claude Code (règle CLAUDE.md : ne jamais commit ceci)
|
||||||
|
.claude/
|
||||||
|
CLAUDE.md
|
||||||
1
.npmrc
Normal file
1
.npmrc
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
@lightweight-sdk:registry=https://git.dev.kyuno.fr/api/packages/kyuno/npm/
|
||||||
35
README.md
35
README.md
|
|
@ -1,3 +1,36 @@
|
||||||
# lightweight-sdk
|
# lightweight-sdk
|
||||||
|
|
||||||
Bibliothèque personnelle de packages TypeScript (monorepo pnpm), publiée sur le registre npm privé Forgejo.
|
Bibliothèque personnelle de packages TypeScript, organisée en monorepo pnpm et publiée sur le registre npm privé hébergé sur [git.dev.kyuno.fr](https://git.dev.kyuno.fr/kyuno).
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
Les packages sont répartis en trois catégories sous `packages/` :
|
||||||
|
|
||||||
|
- `client/` — packages destinés au navigateur (DOM, composants UI).
|
||||||
|
- `common/` — code isomorphe partagé entre client et server.
|
||||||
|
- `server/` — packages Node/backend, structurés en services modulaires autonomes.
|
||||||
|
|
||||||
|
Voir [`spec/architecture.md`](./spec/architecture.md) pour le détail des choix d'outillage, et [`spec/packages.md`](./spec/packages.md) pour la liste des packages existants.
|
||||||
|
|
||||||
|
## Démarrage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
pnpm build
|
||||||
|
pnpm lint
|
||||||
|
pnpm test
|
||||||
|
pnpm docs:check
|
||||||
|
```
|
||||||
|
|
||||||
|
## Créer un nouveau package
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm create-package <client|common|server> <nom-du-package>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- [`spec/architecture.md`](./spec/architecture.md) — vue d'ensemble et choix techniques.
|
||||||
|
- [`spec/conventions.md`](./spec/conventions.md) — conventions de code et traçabilité avec les directives du projet.
|
||||||
|
- [`spec/ci-cd.md`](./spec/ci-cd.md) — CI, versioning (Changesets) et publication sur le registre.
|
||||||
|
- [`spec/packages.md`](./spec/packages.md) — registre des packages du monorepo.
|
||||||
|
|
|
||||||
62
biome.json
Normal file
62
biome.json
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/2.5.14/schema.json",
|
||||||
|
"vcs": {
|
||||||
|
"enabled": true,
|
||||||
|
"clientKind": "git",
|
||||||
|
"useIgnoreFile": true
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"ignoreUnknown": false
|
||||||
|
},
|
||||||
|
"formatter": {
|
||||||
|
"enabled": true,
|
||||||
|
"indentStyle": "tab",
|
||||||
|
"indentWidth": 2,
|
||||||
|
"lineWidth": 100
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"enabled": true,
|
||||||
|
"rules": {
|
||||||
|
"preset": "recommended",
|
||||||
|
"style": {
|
||||||
|
"useConsistentMemberAccessibility": {
|
||||||
|
"level": "error",
|
||||||
|
"options": { "accessibility": "explicit" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nursery": {
|
||||||
|
"noFloatingPromises": "error",
|
||||||
|
"noMisusedPromises": "error",
|
||||||
|
"useAwaitThenable": "error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"javascript": {
|
||||||
|
"formatter": {
|
||||||
|
"quoteStyle": "single",
|
||||||
|
"semicolons": "always",
|
||||||
|
"trailingCommas": "all"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"assist": {
|
||||||
|
"enabled": true,
|
||||||
|
"actions": {
|
||||||
|
"source": {
|
||||||
|
"organizeImports": "on"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"includes": ["packages/client/web-component/src/**"],
|
||||||
|
"formatter": { "enabled": false },
|
||||||
|
"linter": {
|
||||||
|
"rules": {
|
||||||
|
"suspicious": {
|
||||||
|
"noShadowRestrictedNames": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
34
package.json
Normal file
34
package.json
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"name": "lightweight-sdk",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"packageManager": "pnpm@12.4.2",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc -b",
|
||||||
|
"build:clean": "tsc -b --clean",
|
||||||
|
"dev": "tsc -b --watch",
|
||||||
|
"lint": "biome check .",
|
||||||
|
"lint:fix": "biome check --write .",
|
||||||
|
"format": "biome format --write .",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:watch": "vitest",
|
||||||
|
"docs:check": "typedoc --options typedoc.json --emit none --treatWarningsAsErrors --disableGit",
|
||||||
|
"docs:build": "typedoc --options typedoc.json",
|
||||||
|
"changeset": "changeset",
|
||||||
|
"changeset:version": "changeset version",
|
||||||
|
"changeset:publish": "changeset publish",
|
||||||
|
"create-package": "node ./scripts/create-package.mjs",
|
||||||
|
"ci": "pnpm lint && pnpm build && pnpm test && pnpm docs:check"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@biomejs/biome": "catalog:",
|
||||||
|
"@changesets/cli": "catalog:",
|
||||||
|
"@types/node": "catalog:",
|
||||||
|
"typedoc": "catalog:",
|
||||||
|
"typescript": "catalog:",
|
||||||
|
"vitest": "catalog:"
|
||||||
|
}
|
||||||
|
}
|
||||||
48
packages/client/web-component/README.md
Normal file
48
packages/client/web-component/README.md
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
# lightweight-sdk-web-component
|
||||||
|
|
||||||
|
A lightweight library to build web components easily.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm add @lightweight-sdk/web-component
|
||||||
|
```
|
||||||
|
|
||||||
|
## Prérequis côté consommateur
|
||||||
|
|
||||||
|
`@LinkToDom` et `@CustomComponent` sont des décorateurs TypeScript "legacy" (signature `(target, key)` / `(constructor)`). Pour les utiliser, ton propre `tsconfig.json` doit activer :
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"experimentalDecorators": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { AbstractComponent, CustomComponent, LinkToDom } from "@lightweight-sdk/web-component";
|
||||||
|
|
||||||
|
@CustomComponent({
|
||||||
|
tagName: "my-widget",
|
||||||
|
template: "<span ref=\"label\"></span>",
|
||||||
|
})
|
||||||
|
class MyWidget extends AbstractComponent {
|
||||||
|
@LinkToDom("label")
|
||||||
|
private label!: HTMLElement;
|
||||||
|
|
||||||
|
public override init(): void {
|
||||||
|
this.label.textContent = "Hello";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
- `AbstractComponent` — classe de base à étendre pour déclarer un web component (cycle de vie `init`/`refresh`).
|
||||||
|
- `CustomComponent(params)` — décorateur de classe qui enregistre le custom element et branche le template/Shadow DOM.
|
||||||
|
- `LinkToDom(ref)` — décorateur de propriété qui lie un membre de la classe à l'élément DOM portant l'attribut `ref="..."` correspondant.
|
||||||
|
- `findRefElement(ref, parent?)` — retrouve un élément par son attribut `ref` dans un conteneur donné (`document.body` par défaut).
|
||||||
|
- `ComponentParams`, `LinkedProperty`, `AcceptedHTMLContent` — types associés.
|
||||||
28
packages/client/web-component/package.json
Normal file
28
packages/client/web-component/package.json
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"name": "@lightweight-sdk/web-component",
|
||||||
|
"version": "0.0.2",
|
||||||
|
"description": "A lightweight library to build web components easily",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "restricted",
|
||||||
|
"registry": "https://git.dev.kyuno.fr/api/packages/kyuno/npm/"
|
||||||
|
},
|
||||||
|
"typedocOptions": {
|
||||||
|
"entryPoints": [
|
||||||
|
"src/index.ts"
|
||||||
|
],
|
||||||
|
"readme": "README.md",
|
||||||
|
"validation": {
|
||||||
|
"notExported": false,
|
||||||
|
"invalidLink": false,
|
||||||
|
"notDocumented": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
packages/client/web-component/src/abstractComponent.ts
Normal file
30
packages/client/web-component/src/abstractComponent.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { ComponentParams, CUSTOM_COMPONENT_CONFIG, LinkedProperty } from "./component.type";
|
||||||
|
|
||||||
|
/** Basic class used to declare web components */
|
||||||
|
export class AbstractComponent extends HTMLElement {
|
||||||
|
|
||||||
|
/** List of all the class members linked to a dom element */
|
||||||
|
public __linkedProperties?: LinkedProperty[]
|
||||||
|
/** All the component's params */
|
||||||
|
public [CUSTOM_COMPONENT_CONFIG]?: ComponentParams;
|
||||||
|
|
||||||
|
public connectedCallback(): void {
|
||||||
|
this.init();
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public disconnectedCallback(): void { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function called in the component's life cycle
|
||||||
|
* Used to init the DOM and the sub components
|
||||||
|
*/
|
||||||
|
public init(): void { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function called in the component's life cycle
|
||||||
|
* Used to refresh the datas, and re render the component
|
||||||
|
* Please be conscious that if you clean THIS as a dom element, the template won't be re rendered again
|
||||||
|
*/
|
||||||
|
public refresh(): void { }
|
||||||
|
}
|
||||||
110
packages/client/web-component/src/component.tools.ts
Normal file
110
packages/client/web-component/src/component.tools.ts
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
import { AbstractComponent } from "./abstractComponent";
|
||||||
|
import { ComponentParams, CUSTOM_COMPONENT_CONFIG } from "./component.type";
|
||||||
|
import { findRefElement, parseModuleToHtml } from "./dom.tools";
|
||||||
|
|
||||||
|
// Declare a unique key to store the flag on the prototype.
|
||||||
|
// This symbol is used to uniquely identify and access the DOM link flag,
|
||||||
|
// ensuring there are no conflicts with other properties on the prototype.
|
||||||
|
export const REQUEST_DOM_LINK_KEY = Symbol("requestDomLink");
|
||||||
|
|
||||||
|
interface ConstructorFunction<T> {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
new(...args: any[]): T
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a custom web component using a decorator.
|
||||||
|
* The decorator configures the component's template, styles, and any additional
|
||||||
|
* properties, as well as handling Shadow DOM attachment if specified.
|
||||||
|
* @param p @see {@link ComponentParams}
|
||||||
|
* @returns a constructor factory
|
||||||
|
*/
|
||||||
|
export function CustomComponent<T extends ConstructorFunction<AbstractComponent>>(p: ComponentParams): (constructor: T) => void {
|
||||||
|
return (constructor: T): void => {
|
||||||
|
|
||||||
|
// Create a template element to contain the component's HTML structure, parsed for use.
|
||||||
|
const templateElement = document.createElement('template');
|
||||||
|
templateElement.innerHTML = parseModuleToHtml(p.template);
|
||||||
|
|
||||||
|
// Attach component configuration as a non-writable property on the prototype
|
||||||
|
Object.defineProperty(constructor.prototype, CUSTOM_COMPONENT_CONFIG, {
|
||||||
|
value: p
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save the original connectedCallback, if it exists, to allow for extension.
|
||||||
|
const originalConnectedCallback = constructor.prototype.connectedCallback;
|
||||||
|
|
||||||
|
// Define a custom connectedCallback for the component
|
||||||
|
constructor.prototype.connectedCallback = function (this: AbstractComponent) {
|
||||||
|
|
||||||
|
// If Shadow DOM is specified, attach it and add the template to it
|
||||||
|
if (p.attachToShadow) {
|
||||||
|
// Determine Shadow DOM mode, defaulting to 'open' if boolean true is given.
|
||||||
|
const mode: ShadowRootMode = typeof p.attachToShadow === "boolean" ? "open" : p.attachToShadow;
|
||||||
|
|
||||||
|
// Attach a Shadow DOM if not already attached
|
||||||
|
if (!this.shadowRoot) {
|
||||||
|
this.attachShadow({ mode });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append the template content to the Shadow DOM
|
||||||
|
if (this.shadowRoot) {
|
||||||
|
this.shadowRoot.appendChild(templateElement.content.cloneNode(true));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If not using Shadow DOM, insert template content directly into Light DOM
|
||||||
|
this.innerHTML = "";
|
||||||
|
this.appendChild(templateElement.content.cloneNode(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply any specified CSS classes to the component root element
|
||||||
|
if (p.cssClasses) {
|
||||||
|
this.classList.add(...p.cssClasses);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process and link properties specified in __linkedProperties to their DOM elements
|
||||||
|
for (const requested of this.__linkedProperties ?? []) {
|
||||||
|
const elt = findRefElement(requested.ref, this);
|
||||||
|
if (elt == null) {
|
||||||
|
throw new Error(`ref ${requested.ref} not found`);
|
||||||
|
}
|
||||||
|
// Attach the found element as a property on the component instance
|
||||||
|
Object.defineProperty(this, requested.property, { value: elt });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the original connectedCallback if it exists
|
||||||
|
if (originalConnectedCallback) {
|
||||||
|
originalConnectedCallback.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register the custom element with the specified tag name
|
||||||
|
window.customElements.define(p.tagName, constructor);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorator that flags the class member as a link to a dom element
|
||||||
|
* @param ref
|
||||||
|
*/
|
||||||
|
export function LinkToDom(ref: string) {
|
||||||
|
return function <T extends AbstractComponent>(target: T, key: string | symbol): void {
|
||||||
|
// Si target est indéfini, renvoyer un avertissement
|
||||||
|
if (!target) {
|
||||||
|
throw new Error(`LinkToDom: 'target' is undefined for property ${String(key)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ajouter un flag `request` pour marquer cette propriété comme liée au DOM
|
||||||
|
if (!target.__linkedProperties) {
|
||||||
|
target.__linkedProperties = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ajouter la propriété à la liste des propriétés liées
|
||||||
|
target.__linkedProperties.push({
|
||||||
|
property: String(key),
|
||||||
|
ref
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
28
packages/client/web-component/src/component.type.ts
Normal file
28
packages/client/web-component/src/component.type.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
|
||||||
|
export const CUSTOM_COMPONENT_CONFIG = "__customComponentConfig"
|
||||||
|
|
||||||
|
/** Abstract component params*/
|
||||||
|
export interface ComponentParams {
|
||||||
|
/**
|
||||||
|
* A tag name that will be used to register the component as a custom element
|
||||||
|
* Should always follow the following format "my-name"
|
||||||
|
* ex: "custom-menu-component"
|
||||||
|
*/
|
||||||
|
tagName: string;
|
||||||
|
/** HTML Template */
|
||||||
|
template?: string;
|
||||||
|
/** Specific style of the component */
|
||||||
|
style?: string;
|
||||||
|
/** Css classes to append */
|
||||||
|
cssClasses?: string[];
|
||||||
|
/** Define if the component should be atatched to the shadow dom */
|
||||||
|
attachToShadow?: boolean | ShadowRootMode
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Map a property key to a html element by it's ref attribute */
|
||||||
|
export interface LinkedProperty {
|
||||||
|
/** The ref attribute value */
|
||||||
|
ref: string;
|
||||||
|
/** The object's property */
|
||||||
|
property: string;
|
||||||
|
}
|
||||||
26
packages/client/web-component/src/dom.tools.ts
Normal file
26
packages/client/web-component/src/dom.tools.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
|
||||||
|
/** The attriute used to bind dom to component's members */
|
||||||
|
const REF_ATTRIBUTE = "ref"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find an element with the attribute ref= in the given parent (or the entire body)
|
||||||
|
* @param ref the ref attribute's value of the target dom element
|
||||||
|
* @param parent the container used to search the element. document.body by default
|
||||||
|
* @returns the founded html element or undefined
|
||||||
|
*/
|
||||||
|
export function findRefElement(ref: string, parent?: HTMLElement): HTMLElement | undefined {
|
||||||
|
const parentElement = parent ?? document.body;
|
||||||
|
const elt = parentElement.querySelector(`[${REF_ATTRIBUTE}=${ref}]`);
|
||||||
|
|
||||||
|
return elt == null ? undefined : elt as HTMLElement
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the value of ComponentParams.template into a string if it was fetched with required function
|
||||||
|
* @param mod the template value
|
||||||
|
* @returns the template as a string
|
||||||
|
*/
|
||||||
|
export function parseModuleToHtml(mod: any): string {
|
||||||
|
if (mod == null) return "";
|
||||||
|
return "default" in mod ? mod.default as string : "";
|
||||||
|
}
|
||||||
2
packages/client/web-component/src/dom.types.ts
Normal file
2
packages/client/web-component/src/dom.types.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/** Anything that could be append in html*/
|
||||||
|
export type AcceptedHTMLContent = HTMLElement | string
|
||||||
6
packages/client/web-component/src/index.ts
Normal file
6
packages/client/web-component/src/index.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
export { AbstractComponent } from "./abstractComponent";
|
||||||
|
export { CustomComponent, LinkToDom } from "./component.tools";
|
||||||
|
export { ComponentParams, LinkedProperty } from "./component.type";
|
||||||
|
export { findRefElement } from "./dom.tools";
|
||||||
|
export { AcceptedHTMLContent } from "./dom.types";
|
||||||
|
|
||||||
9
packages/client/web-component/tsconfig.json
Normal file
9
packages/client/web-component/tsconfig.json
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"extends": "../../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src"
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"]
|
||||||
|
}
|
||||||
2145
pnpm-lock.yaml
Normal file
2145
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load diff
18
pnpm-workspace.yaml
Normal file
18
pnpm-workspace.yaml
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
packages:
|
||||||
|
- "packages/*/*"
|
||||||
|
|
||||||
|
allowBuilds:
|
||||||
|
esbuild: true
|
||||||
|
|
||||||
|
# human-id@4.1+ est publié en ESM pur ; @changesets/write le require() en CJS
|
||||||
|
# et casse (ERR_REQUIRE_ESM). 4.0.0 est la dernière version CommonJS.
|
||||||
|
overrides:
|
||||||
|
human-id: "4.0.0"
|
||||||
|
|
||||||
|
catalog:
|
||||||
|
typescript: ^5.7.0
|
||||||
|
"@types/node": ^22.10.0
|
||||||
|
vitest: ^2.1.0
|
||||||
|
"@biomejs/biome": ^2.4.0
|
||||||
|
typedoc: ^0.28.0
|
||||||
|
"@changesets/cli": ^2.27.0
|
||||||
153
scripts/create-package.mjs
Normal file
153
scripts/create-package.mjs
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
import { execFileSync } from 'node:child_process';
|
||||||
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
||||||
|
import { dirname, join } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
||||||
|
const CATEGORIES = ['client', 'common', 'server'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints usage information and exits with an error status.
|
||||||
|
* @param {string} message the reason the script is aborting
|
||||||
|
*/
|
||||||
|
function fail(message) {
|
||||||
|
console.error(`Erreur : ${message}`);
|
||||||
|
console.error('Usage : pnpm create-package <client|common|server> <nom-du-package>');
|
||||||
|
process.exitCode = 1;
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, , category, name] = process.argv;
|
||||||
|
|
||||||
|
if (!category || !CATEGORIES.includes(category)) {
|
||||||
|
fail(`catégorie invalide (attendu : ${CATEGORIES.join(', ')})`);
|
||||||
|
}
|
||||||
|
if (!name || !/^[a-z][a-z0-9-]*[a-z0-9]$/.test(name)) {
|
||||||
|
fail('nom de package invalide (attendu : kebab-case, ex. "my-widget")');
|
||||||
|
}
|
||||||
|
|
||||||
|
const packageDir = join(ROOT, 'packages', category, name);
|
||||||
|
if (existsSync(packageDir)) {
|
||||||
|
fail(`packages/${category}/${name} existe déjà`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const npmName = `@lightweight-sdk/${name}`;
|
||||||
|
const isClient = category === 'client';
|
||||||
|
|
||||||
|
mkdirSync(join(packageDir, 'src'), { recursive: true });
|
||||||
|
|
||||||
|
const packageJson = {
|
||||||
|
name: npmName,
|
||||||
|
version: '0.0.1',
|
||||||
|
description: `TODO: décrire le package ${name}.`,
|
||||||
|
main: 'dist/index.js',
|
||||||
|
types: 'dist/index.d.ts',
|
||||||
|
files: ['dist'],
|
||||||
|
engines: { node: '>=22' },
|
||||||
|
publishConfig: {
|
||||||
|
access: 'restricted',
|
||||||
|
registry: 'https://git.dev.kyuno.fr/api/packages/kyuno/npm/',
|
||||||
|
},
|
||||||
|
typedocOptions: {
|
||||||
|
entryPoints: ['src/index.ts'],
|
||||||
|
readme: 'README.md',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
writeFileSync(join(packageDir, 'package.json'), `${JSON.stringify(packageJson, null, '\t')}\n`);
|
||||||
|
|
||||||
|
const tsconfig = {
|
||||||
|
extends: '../../../tsconfig.base.json',
|
||||||
|
compilerOptions: isClient
|
||||||
|
? { lib: ['ES2022', 'DOM', 'DOM.Iterable'], outDir: './dist', rootDir: './src' }
|
||||||
|
: { outDir: './dist', rootDir: './src' },
|
||||||
|
include: ['src/**/*'],
|
||||||
|
};
|
||||||
|
writeFileSync(join(packageDir, 'tsconfig.json'), `${JSON.stringify(tsconfig, null, '\t')}\n`);
|
||||||
|
|
||||||
|
const indexTs = `/**
|
||||||
|
* Placeholder function for the ${npmName} package — replace with real exports.
|
||||||
|
* @param subject the name to greet
|
||||||
|
* @returns a greeting for the given subject
|
||||||
|
*/
|
||||||
|
export function greet(subject: string): string {
|
||||||
|
return \`Hello, \${subject}!\`;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
writeFileSync(join(packageDir, 'src', 'index.ts'), indexTs);
|
||||||
|
|
||||||
|
const indexTest = `import { describe, expect, it } from 'vitest';
|
||||||
|
import { greet } from './index';
|
||||||
|
|
||||||
|
describe('greet', () => {
|
||||||
|
it('greets the given subject', () => {
|
||||||
|
expect(greet('world')).toBe('Hello, world!');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
writeFileSync(join(packageDir, 'src', 'index.test.ts'), indexTest);
|
||||||
|
|
||||||
|
const commonNote =
|
||||||
|
category === 'common'
|
||||||
|
? "\n> `common/` doit rester isomorphe : pas d'API exclusivement DOM ou exclusivement Node.\n"
|
||||||
|
: '';
|
||||||
|
const readme = `# ${npmName}
|
||||||
|
${commonNote}
|
||||||
|
TODO: décrire le package.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
pnpm add ${npmName}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
TODO
|
||||||
|
`;
|
||||||
|
writeFileSync(join(packageDir, 'README.md'), readme);
|
||||||
|
|
||||||
|
// Édite le tsconfig.json racine (project references)
|
||||||
|
const rootTsconfigPath = join(ROOT, 'tsconfig.json');
|
||||||
|
const rootTsconfig = JSON.parse(readFileSync(rootTsconfigPath, 'utf8'));
|
||||||
|
rootTsconfig.references ??= [];
|
||||||
|
rootTsconfig.references.push({ path: `./packages/${category}/${name}` });
|
||||||
|
writeFileSync(rootTsconfigPath, `${JSON.stringify(rootTsconfig, null, '\t')}\n`);
|
||||||
|
|
||||||
|
// Ajoute une ligne au registre des packages
|
||||||
|
const packagesSpecPath = join(ROOT, 'spec', 'packages.md');
|
||||||
|
const packagesSpec = readFileSync(packagesSpecPath, 'utf8');
|
||||||
|
const newRow = `| \`${npmName}\` | ${category} | 0.0.1 | brouillon |\n`;
|
||||||
|
writeFileSync(packagesSpecPath, packagesSpec.trimEnd() + '\n' + newRow);
|
||||||
|
|
||||||
|
console.log(`Package ${npmName} créé dans packages/${category}/${name}/.`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
execFileSync('pnpm', ['install'], { cwd: ROOT, stdio: 'inherit' });
|
||||||
|
execFileSync(
|
||||||
|
'pnpm',
|
||||||
|
[
|
||||||
|
'exec',
|
||||||
|
'biome',
|
||||||
|
'check',
|
||||||
|
'--write',
|
||||||
|
`packages/${category}/${name}`,
|
||||||
|
'tsconfig.json',
|
||||||
|
'spec/packages.md',
|
||||||
|
],
|
||||||
|
{ cwd: ROOT, stdio: 'inherit' },
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Étape automatique échouée, à relancer toi-même :');
|
||||||
|
console.error(' pnpm install');
|
||||||
|
console.error(
|
||||||
|
` pnpm exec biome check --write packages/${category}/${name} tsconfig.json spec/packages.md`,
|
||||||
|
);
|
||||||
|
console.error(String(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\nProchaines étapes :');
|
||||||
|
console.log(` cd packages/${category}/${name}`);
|
||||||
|
console.log(' - compléter la description dans package.json et README.md');
|
||||||
|
console.log(` - pnpm --filter ${npmName} test`);
|
||||||
|
console.log(' - pnpm changeset (une fois prêt à publier)');
|
||||||
39
spec/architecture.md
Normal file
39
spec/architecture.md
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
# Architecture
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
`lightweight-sdk` est un monorepo pnpm destiné à héberger, au fil du temps, des packages TypeScript réutilisables développés par l'auteur pour ses projets personnels. Chaque package est publié indépendamment sur le registre npm privé hébergé sur l'instance Forgejo personnelle (`git.dev.kyuno.fr`), sous le scope `@lightweight-sdk`.
|
||||||
|
|
||||||
|
## Organisation des packages
|
||||||
|
|
||||||
|
Les packages sont répartis en trois catégories sous `packages/`, qui reflètent la distinction Frontend/Backend du [CLAUDE.md](../CLAUDE.md) du projet :
|
||||||
|
|
||||||
|
- **`client/`** — packages destinés au navigateur (DOM, composants UI). Peuvent dépendre de `lib: DOM` dans leur `tsconfig.json`.
|
||||||
|
- **`common/`** — code isomorphe partagé entre `client` et `server` (types, utilitaires purs, constantes). Convention : ne doit dépendre d'aucune API exclusivement DOM ou exclusivement Node, pour rester portable des deux côtés.
|
||||||
|
- **`server/`** — packages Node/backend. Chaque package doit être structuré comme un service modulaire autonome exposant sa propre API (règle "Architecture Backend" du CLAUDE.md).
|
||||||
|
|
||||||
|
Le nom npm d'un package ne reprend pas le préfixe de catégorie (`@lightweight-sdk/web-component`, pas `@lightweight-sdk/client-web-component`) — la catégorie est une convention d'organisation du dépôt, pas une convention de nommage public.
|
||||||
|
|
||||||
|
Un nouveau package se crée avec `pnpm create-package <client|common|server> <nom>` (voir `scripts/create-package.mjs`).
|
||||||
|
|
||||||
|
## Choix d'outillage
|
||||||
|
|
||||||
|
| Aspect | Choix | Pourquoi |
|
||||||
|
|---|---|---|
|
||||||
|
| Gestionnaire de packages | pnpm workspaces + `catalog:` | Centralise les versions des devDependencies partagées dans `pnpm-workspace.yaml` |
|
||||||
|
| Orchestrateur de build | Aucun (pas de Turborepo/Nx) | Un seul package réel au démarrage ; à reconsidérer si le temps de build devient un problème |
|
||||||
|
| Build | `tsc -b` (project references), pas de bundler | Les packages sont consommés via pnpm par d'autres projets Node/TS de l'auteur ; pas besoin de bundle |
|
||||||
|
| Modules | **CommonJS**, résolution `Node10` | Décision explicite : pas d'extension `.js` requise dans les imports relatifs, et le code migré depuis l'ancien repo `web-component` compile sans modification |
|
||||||
|
| Lint + format | [Biome.js](https://biomejs.dev/) | Imposé par le CLAUDE.md du projet |
|
||||||
|
| Documentation de code | [TypeDoc](https://typedoc.org/) en mode validation | Génère la doc API et fait échouer la CI si un export n'a pas de TSDoc (voir `conventions.md`) |
|
||||||
|
| Tests | [Vitest](https://vitest.dev/) | ESM natif, bon support monorepo |
|
||||||
|
| Versioning/publish | [Changesets](https://github.com/changesets/changesets) | Gère des versions indépendantes par package + changelogs, comprend nativement `workspace:*` |
|
||||||
|
| Node cible | 24 (Active LTS) en CI, `engines: ">=22"` | Node 20 est end-of-life depuis avril 2026 |
|
||||||
|
|
||||||
|
## Dépendances entre packages
|
||||||
|
|
||||||
|
Les dépendances internes entre packages du monorepo utilisent le protocole `workspace:*` (résolu localement par pnpm, jamais via le registre distant). Le registre npm privé n'est utilisé que pour la publication et pour la consommation depuis des projets *externes* à ce monorepo.
|
||||||
|
|
||||||
|
## Registre npm privé
|
||||||
|
|
||||||
|
`https://git.dev.kyuno.fr/api/packages/kyuno/npm/` — voir `ci-cd.md` pour le détail de la publication et de l'authentification.
|
||||||
46
spec/ci-cd.md
Normal file
46
spec/ci-cd.md
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
# CI/CD
|
||||||
|
|
||||||
|
## Workflows
|
||||||
|
|
||||||
|
Les workflows vivent dans `.forgejo/workflows/` (pas `.github/workflows/` — ce dernier n'est probablement pas surveillé par Forgejo Actions sur cette instance).
|
||||||
|
|
||||||
|
### `ci.yml`
|
||||||
|
|
||||||
|
Déclenché uniquement sur `pull_request` vers `main` (pas de trigger `push` séparé sur la branche source : il ferait tourner la même vérification deux fois sur le même commit dès qu'une PR est ouverte — deux statuts distincts pour un seul résultat logique, et une exposition doublée aux flakys du runner). Étapes : install (`--frozen-lockfile`) → `pnpm lint` → `pnpm build` → `pnpm test` → `pnpm docs:check`. Ne nécessite aucun secret (les dépendances internes entre packages passent par `workspace:*`, résolu localement).
|
||||||
|
|
||||||
|
### `release.yml`
|
||||||
|
|
||||||
|
Déclenché sur `push` vers `main` (typiquement après le merge d'une PR "Version Packages"). Étapes : install → build → ajout dynamique d'une ligne d'authentification dans `.npmrc` (jamais committée) → `pnpm changeset:publish`.
|
||||||
|
|
||||||
|
`changeset publish` est idempotent par package : il republie uniquement les packages dont la version locale n'existe pas encore sur le registre. Un push sur `main` sans bump de version ne publie donc rien.
|
||||||
|
|
||||||
|
## Flux Changesets (versioning + publication)
|
||||||
|
|
||||||
|
1. Pendant une PR de feature, ajouter un changeset : `pnpm changeset` (choisir le(s) package(s) concerné(s), le type de bump, décrire le changement). Le fichier généré dans `.changeset/` est commité avec la PR.
|
||||||
|
2. Quand on est prêt à publier, en local : `pnpm changeset version` — bump les `package.json` concernés et génère/met à jour leurs `CHANGELOG.md`.
|
||||||
|
3. Ouvrir une PR "Version Packages" avec ce résultat, la relire, la merger soi-même (jamais d'auto-merge).
|
||||||
|
4. Le merge sur `main` déclenche `release.yml`, qui publie effectivement sur le registre les packages dont la version a changé.
|
||||||
|
|
||||||
|
Pas de bot automatique type `changesets/action` (spécifique GitHub, incertain sur Forgejo) : le flux est volontairement manuel aux étapes 1-3.
|
||||||
|
|
||||||
|
## Registre npm privé Forgejo
|
||||||
|
|
||||||
|
- URL : `https://git.dev.kyuno.fr/api/packages/kyuno/npm/` (owner Forgejo = `kyuno`, indépendant du scope npm `@lightweight-sdk` — les deux ne sont pas liés).
|
||||||
|
- `.npmrc` (racine, committé) : `@lightweight-sdk:registry=https://git.dev.kyuno.fr/api/packages/kyuno/npm/`.
|
||||||
|
- Authentification : `//git.dev.kyuno.fr/api/packages/kyuno/npm/:_authToken=<token>` — jamais committé, injecté uniquement en CI via le secret `REGISTRY_TOKEN`.
|
||||||
|
- Limite connue : impossible de republier un nom+version déjà existant sur le registre (Changesets gère ça nativement en forçant un bump à chaque publication).
|
||||||
|
|
||||||
|
### Secret `REGISTRY_TOKEN`
|
||||||
|
|
||||||
|
1. Forgejo → Settings → Applications → générer un token avec les scopes `write:package` + `read:package`.
|
||||||
|
2. L'ajouter comme secret de repo nommé `REGISTRY_TOKEN` sur **ce** repo (`lightweight-sdk`) — les secrets de repo ne sont pas partagés entre repos sur cette instance ; celui de `watchparty` ne peut pas être réutilisé tel quel.
|
||||||
|
|
||||||
|
### Troubleshooting : 404 sur un package scopé
|
||||||
|
|
||||||
|
Si `pnpm changeset:publish` échoue avec une 404 sur un nom de package scopé (`@lightweight-sdk/...`), la cause probable est le reverse proxy devant l'instance : npm encode le `/` d'un nom scopé en `%2F`, ce qu'Apache refuse par défaut. Vérifier `AllowEncodedSlashes NoDecode` dans le VirtualHost et `nocanon` sur la directive `ProxyPass`. Ce point n'a pas pu être vérifié depuis l'agent (config réseau propre à l'instance).
|
||||||
|
|
||||||
|
## Pistes futures
|
||||||
|
|
||||||
|
- `git push --follow-tags` après `changeset publish` pour taguer les releases (pas fait par défaut aujourd'hui).
|
||||||
|
- Automatiser l'étape 2 du flux Changesets (PR "Version Packages" ouverte automatiquement) via un workflow `workflow_dispatch` ou planifié.
|
||||||
|
- Protection de branche réelle sur `main` côté Forgejo (l'interdiction de push direct n'est aujourd'hui que conventionnelle, via CLAUDE.md).
|
||||||
50
spec/conventions.md
Normal file
50
spec/conventions.md
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
# Conventions
|
||||||
|
|
||||||
|
## Traçabilité des règles CLAUDE.md
|
||||||
|
|
||||||
|
| Règle CLAUDE.md | Mécanisme dans ce repo |
|
||||||
|
|---|---|
|
||||||
|
| Modificateurs d'accès explicites | Biome `useConsistentMemberAccessibility: "explicit"` (partiellement auto-fixable via `pnpm lint:fix`) ✅ |
|
||||||
|
| TSDoc sur chaque fonction/type/interface/classe/membre/méthode | TypeDoc en mode validation (`pnpm docs:check`, bloquant en CI). Biome n'a pas de règle équivalente ✅ (sauf `@lightweight-sdk/web-component`, exclu — voir `packages.md`) |
|
||||||
|
| `.catch()` obligatoire sur toute Promise non-awaited | Biome `noFloatingPromises` / `noMisusedPromises` / `useAwaitThenable` ✅ |
|
||||||
|
| `try/catch` obligatoire autour de chaque `await` individuel | **Aucun linter JS/TS courant ne vérifie ça** (ni Biome ni ESLint). Convention à appliquer soi-même à chaque nouveau package ❌ mécanisme automatique |
|
||||||
|
| Indentation "2 tabulations" | `biome.json` (`indentStyle: "tab"`, `indentWidth: 2`) + `.editorconfig` ⚠️ deux exceptions : les fichiers YAML n'acceptent pas les tabulations (interdit par la spec YAML) → 2 espaces ; le Markdown n'est pas formaté par Biome → 2 espaces par convention manuelle |
|
||||||
|
| Logging exhaustif | Convention à appliquer dans chaque nouveau package (pas de mécanisme automatique — à illustrer dans le code) |
|
||||||
|
| Séparation `.ts`/`.html`/`.scss` (Frontend) | S'applique aux futurs packages `client/` qui rendent des composants concrets avec leur propre template. Ne s'applique pas à `web-component`, qui est une bibliothèque d'abstraction sans template propre (et de toute façon non modifiée, voir `packages.md`) |
|
||||||
|
| Services modulaires autonomes (Backend) | Chaque package `server/` doit exposer sa propre API, indépendante des autres |
|
||||||
|
|
||||||
|
`pnpm lint` (= `biome check .`) tourne **sans** `--error-on-warnings` : les diagnostics de sévérité "warn" restent visibles mais ne font pas échouer la CI. Seules les erreurs bloquent.
|
||||||
|
|
||||||
|
## Imports
|
||||||
|
|
||||||
|
Sous `module: CommonJS` + `moduleResolution: Node10` (voir `architecture.md`), les imports relatifs **n'ont pas besoin** d'extension (`from "./foo"`, pas `from "./foo.js"`).
|
||||||
|
|
||||||
|
## Nommage
|
||||||
|
|
||||||
|
- Dossiers de package et noms npm : kebab-case (`@lightweight-sdk/my-package`).
|
||||||
|
- Classes : PascalCase. Fonctions/variables : camelCase.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
- Colocalisés : `src/foo.ts` + `src/foo.test.ts`.
|
||||||
|
- Environnement Vitest par défaut : `"node"`. Pour un test qui a besoin du DOM, utiliser le pragma en première ligne du fichier : `// @vitest-environment happy-dom` (ajouter `happy-dom` en devDependency le jour où c'est nécessaire).
|
||||||
|
|
||||||
|
## Exemple : convention async/erreurs/logging attendue dans un nouveau package
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { logger } from './logger';
|
||||||
|
|
||||||
|
/** Fetches and parses the remote config, throwing on failure. */
|
||||||
|
export async function loadConfig(url: string): Promise<Config> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('loadConfig failed', { url, error });
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appel fire-and-forget : .catch() obligatoire (pas d'await)
|
||||||
|
void loadConfig(configUrl).catch((error) => logger.error('background config load failed', { error }));
|
||||||
|
```
|
||||||
32
spec/packages.md
Normal file
32
spec/packages.md
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Registre des packages
|
||||||
|
|
||||||
|
| Package | Catégorie | Version | Statut |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `@lightweight-sdk/web-component` | client | 0.0.2 | migré depuis `lightweight-sdk-web-component`, non modifié |
|
||||||
|
|
||||||
|
## Ajouter un package
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm create-package <client|common|server> <nom-du-package>
|
||||||
|
```
|
||||||
|
|
||||||
|
Le script scaffolde `package.json`, `tsconfig.json`, `src/index.ts` + un test, un `README.md`, ajoute le package aux project references racine et met à jour ce tableau automatiquement. Reste à faire à la main : décrire le package, l'implémenter, et créer un changeset (`pnpm changeset`) quand il est prêt à être publié.
|
||||||
|
|
||||||
|
## Champs `package.json` obligatoires pour tout package du monorepo
|
||||||
|
|
||||||
|
- `name` (`@lightweight-sdk/<nom>`), `version`, `description`
|
||||||
|
- `main` (`dist/index.js`), `types` (`dist/index.d.ts`), `files: ["dist"]`
|
||||||
|
- `engines.node >= 22`
|
||||||
|
- `publishConfig.registry` pointant vers le registre npm privé (`https://git.dev.kyuno.fr/api/packages/kyuno/npm/`)
|
||||||
|
- `typedocOptions.entryPoints` (sauf exception documentée, voir ci-dessous)
|
||||||
|
|
||||||
|
## Observations sur `@lightweight-sdk/web-component` (non traitées, laissées telles quelles)
|
||||||
|
|
||||||
|
Le code source de ce package a été migré strictement à l'identique depuis l'ancien repo `lightweight-sdk-web-component`, sans aucune édition (décision explicite de l'auteur). Quelques points relevés à la lecture, non corrigés, à traiter soi-même dans une PR dédiée si souhaité :
|
||||||
|
|
||||||
|
- `connectedCallback()`/`disconnectedCallback()` (`abstractComponent.ts`), la constante `CUSTOM_COMPONENT_CONFIG` (`component.type.ts`) et `REQUEST_DOM_LINK_KEY` (`component.tools.ts`) n'ont pas de TSDoc complet — c'est pourquoi ce package est exclu de la validation `docs:check` dans `typedoc.json` (`exclude: ["packages/client/web-component/**"]`).
|
||||||
|
- Deux usages de `any` (`ConstructorFunction.new(...args: any[])`, `parseModuleToHtml(mod: any)`) — visibles comme avertissements Biome non bloquants (`noExplicitAny`).
|
||||||
|
- Le paramètre `constructor` dans `component.tools.ts` masque un nom global restreint (`noShadowRestrictedNames`) — désactivé pour ce package via un `overrides` dans `biome.json`.
|
||||||
|
- Le style de formatage d'origine (4 espaces, guillemets doubles) diffère de la config Biome du monorepo (tabulations, guillemets simples) — le formatter Biome est désactivé pour ce package via le même `overrides`.
|
||||||
|
- `REQUEST_DOM_LINK_KEY` semble ne plus être utilisé nulle part dans le code (le mécanisme actif est le tableau `__linkedProperties`) — potentiellement mort, mais pas supprimé.
|
||||||
|
- Quelques typos mineures ("atatched", "attriute") et un commentaire en français dans `LinkToDom` qui ne correspond plus exactement au comportement (`throw` au lieu d'un simple avertissement).
|
||||||
18
tsconfig.base.json
Normal file
18
tsconfig.base.json
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "CommonJS",
|
||||||
|
"moduleResolution": "Node10",
|
||||||
|
"strict": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"composite": true,
|
||||||
|
"incremental": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"experimentalDecorators": true
|
||||||
|
}
|
||||||
|
}
|
||||||
4
tsconfig.json
Normal file
4
tsconfig.json
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [{ "path": "./packages/client/web-component" }]
|
||||||
|
}
|
||||||
27
typedoc.json
Normal file
27
typedoc.json
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://typedoc.org/schema.json",
|
||||||
|
"entryPointStrategy": "packages",
|
||||||
|
"entryPoints": ["packages/*/*"],
|
||||||
|
"out": "docs/api",
|
||||||
|
"validation": {
|
||||||
|
"notExported": true,
|
||||||
|
"invalidLink": true,
|
||||||
|
"notDocumented": true
|
||||||
|
},
|
||||||
|
"requiredToBeDocumented": [
|
||||||
|
"Enum",
|
||||||
|
"EnumMember",
|
||||||
|
"Variable",
|
||||||
|
"Function",
|
||||||
|
"Class",
|
||||||
|
"Interface",
|
||||||
|
"Property",
|
||||||
|
"Method",
|
||||||
|
"Accessor",
|
||||||
|
"TypeAlias",
|
||||||
|
"Parameter",
|
||||||
|
"Constructor"
|
||||||
|
],
|
||||||
|
"treatWarningsAsErrors": true,
|
||||||
|
"excludeExternals": true
|
||||||
|
}
|
||||||
10
vitest.config.ts
Normal file
10
vitest.config.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
include: ['packages/*/*/src/**/*.test.ts'],
|
||||||
|
exclude: ['**/node_modules/**', '**/dist/**'],
|
||||||
|
environment: 'node',
|
||||||
|
passWithNoTests: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue