@hgdc/config 0.0.0-stage → 0.1.1

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/README.md CHANGED
@@ -1,3 +1,21 @@
1
- # Temporary Holding Version
1
+ # @hgdc/config
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Configuration partagée des produits de la H family : presets TypeScript, oxlint et oxfmt, preset Renovate, versions exactes de la stack.
4
+
5
+ ```sh
6
+ bun add --dev --exact @hgdc/config
7
+ bunx hgdc-stack check # échoue si une dépendance de la stack n'est pas à sa version
8
+ bunx hgdc-stack sync # réécrit package.json aux versions de la stack
9
+ ```
10
+
11
+ | Export | Usage |
12
+ | ---------------------------------------------------------- | ----------------------------------- |
13
+ | `@hgdc/config/tsconfig/{base,app,convex,library}.json` | `"extends"` d'un tsconfig |
14
+ | `@hgdc/config/oxlint` | `extends` d'un `oxlint.config.ts` |
15
+ | `@hgdc/config/oxfmt` | à étendre dans un `oxfmt.config.ts` |
16
+ | `@hgdc/config/stack.json` | versions exactes de la stack |
17
+ | `github>hugogebel-boop/hgdc-kit//packages/config/renovate` | preset Renovate |
18
+
19
+ Mode d'emploi complet : [Adopter le kit](https://github.com/hugogebel-boop/hgdc-kit/blob/main/docs/guides/adopter-le-kit.md).
20
+
21
+ Tous droits réservés, Hugo Gebel Développement Conseil.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, writeFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { parseArgs } from "node:util";
5
+ import { alignManifest, findDrift, loadStack, parseManifest } from "./stack.js";
6
+ const USAGE = `Usage : hgdc-stack <check|sync> [--cwd <dossier>]
7
+
8
+ check échoue si une dépendance de la stack n'est pas à sa version exacte
9
+ sync réécrit package.json aux versions de la stack (lancer bun install ensuite)`;
10
+ const { positionals, values } = parseArgs({
11
+ allowPositionals: true,
12
+ options: { cwd: { type: "string", default: process.cwd() } },
13
+ });
14
+ const command = positionals[0];
15
+ if (command !== "check" && command !== "sync") {
16
+ console.error(USAGE);
17
+ process.exit(2);
18
+ }
19
+ const manifestPath = resolve(values.cwd, "package.json");
20
+ const manifest = parseManifest(readFileSync(manifestPath, "utf8"));
21
+ const stack = loadStack();
22
+ const drift = findDrift(manifest, stack);
23
+ if (drift.length === 0) {
24
+ console.log("Stack HGDC : à jour.");
25
+ process.exit(0);
26
+ }
27
+ for (const { name, field, current, expected } of drift) {
28
+ console.log(` ${name} (${field}) : ${current} → ${expected}`);
29
+ }
30
+ if (command === "check") {
31
+ console.error(`\nStack HGDC : ${drift.length} écart(s). Corriger avec « hgdc-stack sync » puis « bun install ».`);
32
+ process.exit(1);
33
+ }
34
+ writeFileSync(manifestPath, `${JSON.stringify(alignManifest(manifest, stack), null, 2)}\n`);
35
+ console.log(`\nStack HGDC : ${drift.length} version(s) réalignée(s). Lancer « bun install ».`);
@@ -0,0 +1,8 @@
1
+ import { type OxfmtConfig } from "oxfmt";
2
+ /**
3
+ * Format commun de la H family, aligné sur le code existant de host et mail
4
+ * (sans point-virgule, guillemets doubles). Le produit étend ce preset et ajoute,
5
+ * s'il utilise Tailwind, `sortTailwindcss.stylesheet` vers sa feuille principale.
6
+ */
7
+ declare const hgdc: OxfmtConfig;
8
+ export default hgdc;
package/dist/oxfmt.js ADDED
@@ -0,0 +1,15 @@
1
+ import { defineConfig } from "oxfmt";
2
+ /**
3
+ * Format commun de la H family, aligné sur le code existant de host et mail
4
+ * (sans point-virgule, guillemets doubles). Le produit étend ce preset et ajoute,
5
+ * s'il utilise Tailwind, `sortTailwindcss.stylesheet` vers sa feuille principale.
6
+ */
7
+ const hgdc = defineConfig({
8
+ printWidth: 120,
9
+ semi: false,
10
+ singleQuote: false,
11
+ trailingComma: "all",
12
+ sortPackageJson: false,
13
+ ignorePatterns: ["**/_generated/**", "**/routeTree.gen.ts", "**/CHANGELOG.md", ".changeset/*.md"],
14
+ });
15
+ export default hgdc;
@@ -0,0 +1,11 @@
1
+ import { type OxlintConfig } from "oxlint";
2
+ /**
3
+ * Règles communes de la H family : la catégorie `correctness` d'oxlint (son défaut recommandé)
4
+ * plus une courte liste de règles à forte valeur, toutes bloquantes. Pas d'avertissement :
5
+ * une règle qui ne bloque pas finit ignorée.
6
+ *
7
+ * À étendre depuis l'`oxlint.config.ts` racine du produit, qui pose aussi `options.typeAware`
8
+ * (oxlint ne lit cette option que dans la config racine).
9
+ */
10
+ declare const hgdc: OxlintConfig;
11
+ export default hgdc;
package/dist/oxlint.js ADDED
@@ -0,0 +1,63 @@
1
+ import { defineConfig } from "oxlint";
2
+ /**
3
+ * Règles communes de la H family : la catégorie `correctness` d'oxlint (son défaut recommandé)
4
+ * plus une courte liste de règles à forte valeur, toutes bloquantes. Pas d'avertissement :
5
+ * une règle qui ne bloque pas finit ignorée.
6
+ *
7
+ * À étendre depuis l'`oxlint.config.ts` racine du produit, qui pose aussi `options.typeAware`
8
+ * (oxlint ne lit cette option que dans la config racine).
9
+ */
10
+ const hgdc = defineConfig({
11
+ plugins: ["eslint", "typescript", "unicorn", "oxc", "import", "promise", "react", "jsx-a11y", "vitest"],
12
+ categories: {
13
+ correctness: "error",
14
+ },
15
+ rules: {
16
+ // Promesse oubliée = écriture Convex perdue ou erreur avalée.
17
+ "typescript/no-floating-promises": "error",
18
+ // Handler async passé en attribut JSX : usage React normal (recommandation typescript-eslint).
19
+ "typescript/no-misused-promises": ["error", { checksVoidReturn: { attributes: false } }],
20
+ "typescript/await-thenable": "error",
21
+ "typescript/no-explicit-any": "error",
22
+ "typescript/consistent-type-imports": "error",
23
+ "eslint/eqeqeq": ["error", "smart"],
24
+ "eslint/no-console": ["error", { allow: ["warn", "error"] }],
25
+ "import/no-cycle": "error",
26
+ "react/rules-of-hooks": "error",
27
+ "react/exhaustive-deps": "error",
28
+ "react/jsx-key": "error",
29
+ // Accessibilité alignée sur le preset `recommended` de eslint-plugin-jsx-a11y.
30
+ "jsx-a11y/prefer-tag-over-role": "off",
31
+ "jsx-a11y/control-has-associated-label": "off",
32
+ "jsx-a11y/no-noninteractive-element-to-interactive-role": [
33
+ "error",
34
+ {
35
+ ul: ["listbox", "menu", "menubar", "radiogroup", "tablist", "tree", "treegrid"],
36
+ ol: ["listbox", "menu", "menubar", "radiogroup", "tablist", "tree", "treegrid"],
37
+ li: ["menuitem", "menuitemradio", "menuitemcheckbox", "option", "row", "tab", "treeitem"],
38
+ table: ["grid"],
39
+ td: ["gridcell"],
40
+ },
41
+ ],
42
+ // Contrôles du kit UI reconnus comme champs ; texte du label souvent imbriqué dans des <span>.
43
+ "jsx-a11y/label-has-associated-control": [
44
+ "error",
45
+ { controlComponents: ["Input", "Textarea", "Select", "Switch", "Checkbox", "RadioGroup"], depth: 3 },
46
+ ],
47
+ // Focus automatique voulu sur les écrans à une seule tâche (connexion, code à usage unique).
48
+ "jsx-a11y/no-autofocus": "off",
49
+ },
50
+ overrides: [
51
+ {
52
+ // Scripts et CLI : la console est leur interface. Convex : console.log alimente les logs du tableau de bord.
53
+ files: ["scripts/**", "**/cli.ts", "**/*.config.ts", "convex/**"],
54
+ rules: { "eslint/no-console": "off" },
55
+ },
56
+ {
57
+ // Composant Label générique (shadcn) : le champ est associé là où il est utilisé.
58
+ files: ["**/components/ui/label.tsx"],
59
+ rules: { "jsx-a11y/label-has-associated-control": "off" },
60
+ },
61
+ ],
62
+ });
63
+ export default hgdc;
@@ -0,0 +1,26 @@
1
+ /** Versions exactes de la stack commune, par nom de paquet. */
2
+ export type Stack = Readonly<Record<string, string>>;
3
+ declare const DEPENDENCY_FIELDS: readonly ["dependencies", "devDependencies", "optionalDependencies"];
4
+ type DependencyField = (typeof DEPENDENCY_FIELDS)[number];
5
+ /** Sous-ensemble d'un package.json utile ici ; les autres champs sont conservés tels quels. */
6
+ export type Manifest = {
7
+ [K in DependencyField]?: Record<string, string>;
8
+ } & Record<string, unknown>;
9
+ export type Drift = {
10
+ name: string;
11
+ field: DependencyField;
12
+ current: string;
13
+ expected: string;
14
+ };
15
+ export declare function parseStack(text: string): Stack;
16
+ export declare function parseManifest(text: string): Manifest;
17
+ export declare function loadStack(): Stack;
18
+ /**
19
+ * Écarts entre un manifeste et la stack. Un paquet absent du manifeste n'est pas un écart :
20
+ * la stack fixe la version de ce qu'un produit utilise, pas la liste de ses dépendances.
21
+ * `peerDependencies` est ignoré : une plage y est voulue.
22
+ */
23
+ export declare function findDrift(manifest: Manifest, stack: Stack): Drift[];
24
+ /** Copie du manifeste où chaque paquet de la stack est épinglé à sa version. */
25
+ export declare function alignManifest(manifest: Manifest, stack: Stack): Manifest;
26
+ export {};
package/dist/stack.js ADDED
@@ -0,0 +1,61 @@
1
+ import { readFileSync } from "node:fs";
2
+ const DEPENDENCY_FIELDS = ["dependencies", "devDependencies", "optionalDependencies"];
3
+ const EXACT_VERSION = /^\d+\.\d+\.\d+(?:-[\w.]+)?$/;
4
+ function isStringRecord(value) {
5
+ return (typeof value === "object" &&
6
+ value !== null &&
7
+ !Array.isArray(value) &&
8
+ Object.values(value).every((v) => typeof v === "string"));
9
+ }
10
+ function isManifest(value) {
11
+ if (typeof value !== "object" || value === null || Array.isArray(value))
12
+ return false;
13
+ const fields = { ...value };
14
+ return DEPENDENCY_FIELDS.every((field) => fields[field] === undefined || isStringRecord(fields[field]));
15
+ }
16
+ export function parseStack(text) {
17
+ const raw = JSON.parse(text);
18
+ if (!isStringRecord(raw))
19
+ throw new Error("stack.json doit associer un nom de paquet à une version");
20
+ const inexact = Object.entries(raw).filter(([, version]) => !EXACT_VERSION.test(version));
21
+ if (inexact.length > 0) {
22
+ throw new Error(`stack.json : versions non exactes : ${inexact.map(([n, v]) => `${n}@${v}`).join(", ")}`);
23
+ }
24
+ return raw;
25
+ }
26
+ export function parseManifest(text) {
27
+ const raw = JSON.parse(text);
28
+ if (!isManifest(raw))
29
+ throw new Error("package.json invalide : un champ de dépendances n'est pas un objet de chaînes");
30
+ return raw;
31
+ }
32
+ export function loadStack() {
33
+ return parseStack(readFileSync(new URL("../stack.json", import.meta.url), "utf8"));
34
+ }
35
+ /**
36
+ * Écarts entre un manifeste et la stack. Un paquet absent du manifeste n'est pas un écart :
37
+ * la stack fixe la version de ce qu'un produit utilise, pas la liste de ses dépendances.
38
+ * `peerDependencies` est ignoré : une plage y est voulue.
39
+ */
40
+ export function findDrift(manifest, stack) {
41
+ const drift = [];
42
+ for (const field of DEPENDENCY_FIELDS) {
43
+ for (const [name, current] of Object.entries(manifest[field] ?? {})) {
44
+ const expected = stack[name];
45
+ if (expected !== undefined && current !== expected)
46
+ drift.push({ name, field, current, expected });
47
+ }
48
+ }
49
+ return drift;
50
+ }
51
+ /** Copie du manifeste où chaque paquet de la stack est épinglé à sa version. */
52
+ export function alignManifest(manifest, stack) {
53
+ const aligned = { ...manifest };
54
+ for (const field of DEPENDENCY_FIELDS) {
55
+ const deps = manifest[field];
56
+ if (!deps)
57
+ continue;
58
+ aligned[field] = Object.fromEntries(Object.entries(deps).map(([name, version]) => [name, stack[name] ?? version]));
59
+ }
60
+ return aligned;
61
+ }
package/package.json CHANGED
@@ -1,6 +1,49 @@
1
1
  {
2
2
  "name": "@hgdc/config",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Config partagée de la H family : tsconfig, oxlint, oxfmt, Renovate, versions exactes de la stack.",
5
+ "license": "UNLICENSED",
6
+ "homepage": "https://github.com/hugogebel-boop/hgdc-kit/tree/main/packages/config#readme",
7
+ "bugs": "https://github.com/hugogebel-boop/hgdc-kit/security/advisories/new",
8
+ "type": "module",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/hugogebel-boop/hgdc-kit.git",
12
+ "directory": "packages/config"
13
+ },
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "tsconfig",
20
+ "renovate",
21
+ "stack.json"
22
+ ],
23
+ "bin": {
24
+ "hgdc-stack": "./dist/cli.js"
25
+ },
26
+ "exports": {
27
+ "./oxlint": {
28
+ "types": "./dist/oxlint.d.ts",
29
+ "default": "./dist/oxlint.js"
30
+ },
31
+ "./oxfmt": {
32
+ "types": "./dist/oxfmt.d.ts",
33
+ "default": "./dist/oxfmt.js"
34
+ },
35
+ "./stack": {
36
+ "types": "./dist/stack.d.ts",
37
+ "default": "./dist/stack.js"
38
+ },
39
+ "./stack.json": "./stack.json",
40
+ "./tsconfig/*": "./tsconfig/*",
41
+ "./package.json": "./package.json"
42
+ },
43
+ "scripts": {
44
+ "build": "tsc -p tsconfig.build.json"
45
+ },
46
+ "engines": {
47
+ "node": ">=22.18.0"
48
+ }
49
+ }
@@ -0,0 +1,54 @@
1
+ {
2
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
+ "description": "Preset Renovate des produits de la H family.",
4
+ "extends": ["config:recommended", ":semanticCommitTypeAll(chore)", "helpers:pinGitHubActionDigests"],
5
+ "timezone": "Europe/Zurich",
6
+ "schedule": ["before 6am on monday"],
7
+ "minimumReleaseAge": "3 days",
8
+ "rangeStrategy": "pin",
9
+ "lockFileMaintenance": {
10
+ "enabled": true,
11
+ "automerge": true
12
+ },
13
+ "packageRules": [
14
+ {
15
+ "description": "Kit et stack commune : propagés par hgdc-kit (propagate.yml), pas par Renovate.",
16
+ "matchPackageNames": [
17
+ "@hgdc/*",
18
+ "@changesets/cli",
19
+ "@convex-dev/better-auth",
20
+ "@convex-dev/react-query",
21
+ "@tailwindcss/vite",
22
+ "@tanstack/react-query",
23
+ "@tanstack/react-router",
24
+ "@tanstack/react-router-ssr-query",
25
+ "@tanstack/react-start",
26
+ "@types/bun",
27
+ "@types/node",
28
+ "@types/react",
29
+ "@types/react-dom",
30
+ "@vitejs/plugin-react",
31
+ "better-auth",
32
+ "convex",
33
+ "nitro",
34
+ "oxfmt",
35
+ "oxlint",
36
+ "oxlint-tsgolint",
37
+ "react",
38
+ "react-dom",
39
+ "tailwindcss",
40
+ "typescript",
41
+ "vite",
42
+ "vitest",
43
+ "zod"
44
+ ],
45
+ "enabled": false
46
+ },
47
+ {
48
+ "description": "Mineurs et correctifs d'une dépendance stable : fusion automatique si la CI passe.",
49
+ "matchUpdateTypes": ["minor", "patch", "pin", "digest"],
50
+ "matchCurrentVersion": "!/^v?0\\./",
51
+ "automerge": true
52
+ }
53
+ ]
54
+ }
package/stack.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "@changesets/cli": "3.0.3",
3
+ "@convex-dev/better-auth": "0.12.5",
4
+ "@convex-dev/react-query": "0.1.0",
5
+ "@tailwindcss/vite": "4.3.3",
6
+ "@tanstack/react-query": "5.104.1",
7
+ "@tanstack/react-router": "1.170.42",
8
+ "@tanstack/react-router-ssr-query": "1.167.3",
9
+ "@tanstack/react-start": "1.168.61",
10
+ "@types/bun": "1.4.2",
11
+ "@types/node": "24.19.2",
12
+ "@types/react": "19.3.0",
13
+ "@types/react-dom": "19.3.0",
14
+ "@vitejs/plugin-react": "6.1.2",
15
+ "better-auth": "1.6.16",
16
+ "convex": "1.46.0",
17
+ "nitro": "3.0.260903-beta",
18
+ "oxfmt": "0.72.0",
19
+ "oxlint": "1.87.0",
20
+ "oxlint-tsgolint": "7.0.2004",
21
+ "react": "19.3.0",
22
+ "react-dom": "19.3.0",
23
+ "tailwindcss": "4.3.3",
24
+ "typescript": "7.0.2",
25
+ "vite": "8.3.4",
26
+ "vitest": "5.0.3",
27
+ "zod": "4.6.5"
28
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "extends": "./base.json",
4
+ "compilerOptions": {
5
+ "lib": ["ES2024", "DOM", "DOM.Iterable"],
6
+ "jsx": "react-jsx",
7
+ "types": ["vite/client"]
8
+ }
9
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "compilerOptions": {
4
+ "target": "ES2024",
5
+ "lib": ["ES2024"],
6
+ "module": "preserve",
7
+ "moduleResolution": "bundler",
8
+ "moduleDetection": "force",
9
+ "resolveJsonModule": true,
10
+ "allowImportingTsExtensions": true,
11
+ "verbatimModuleSyntax": true,
12
+ "isolatedModules": true,
13
+ "strict": true,
14
+ "noUncheckedIndexedAccess": true,
15
+ "noImplicitOverride": true,
16
+ "noFallthroughCasesInSwitch": true,
17
+ "noUnusedLocals": true,
18
+ "noUnusedParameters": true,
19
+ "forceConsistentCasingInFileNames": true,
20
+ "skipLibCheck": true,
21
+ "noEmit": true
22
+ }
23
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "extends": "./base.json",
4
+ "compilerOptions": {
5
+ "target": "ESNext",
6
+ "module": "ESNext",
7
+ "lib": ["ES2024", "DOM"],
8
+ "jsx": "react-jsx",
9
+ "allowJs": true,
10
+ "types": ["node"]
11
+ }
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "extends": "./base.json",
4
+ "compilerOptions": {
5
+ "module": "nodenext",
6
+ "moduleResolution": "nodenext",
7
+ "rewriteRelativeImportExtensions": true,
8
+ "noEmit": false,
9
+ "declaration": true,
10
+ "isolatedDeclarations": true
11
+ }
12
+ }