@hexah/create-skin 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hexah Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # @hexah/create-skin
2
+
3
+ Scaffolder skórki Hexah — generuje projekt **zdalnej skórki Module Federation** (rspack +
4
+ `@hexah/skin-sdk`) z gotowym workflowem dev (HMR) i podłączeniem do hosta na środowisku
5
+ testowym.
6
+
7
+ ## Użycie
8
+
9
+ ```bash
10
+ npm create @hexah/skin moja-skorka # albo: npx @hexah/create-skin moja-skorka
11
+ cd moja-skorka
12
+ npm install
13
+ npm run dev # rspack serve z HMR
14
+ ```
15
+
16
+ Następnie podłącz remote do hosta na teście (instrukcja w wygenerowanym `README.md` skórki):
17
+ `localStorage["hexah.skinRemote"]` + motyw „SDK Remote (zdalna skórka)".
18
+
19
+ ## Co generuje
20
+
21
+ Starter (`template/`) z placeholderami `__SKIN_NAME__` / `__SKIN_FEDERATION_NAME__`:
22
+
23
+ - `rspack.config.js` — `ModuleFederationPlugin` wystawiający `./ReportScreen`, paczki
24
+ współdzielone z hostem (`import: false`), dev server na `:3001` z CORS.
25
+ - `src/ReportScreen.jsx` — przykładowy ekran na `@hexah/skin-sdk`.
26
+ - `package.json`, `.gitignore`, `README.md`.
27
+
28
+ Nazwa kontenera MF jest sanityzowana z nazwy katalogu (`toFederationName` — identyfikator JS).
29
+
30
+ ## Status
31
+
32
+ POC etapu C. Publikowany jako `@hexah/create-skin` (licencja MIT) przez ręczny workflow —
33
+ patrz `docs/skin-sdk/publishing.md`. Przed publikacją działa tylko z repo
34
+ (`node packages/create-hexah-skin/index.js <katalog>`). Pełne demo wymaga środowiska testowego.
package/index.js ADDED
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ /**
5
+ * create-hexah-skin — scaffolder skórki Hexah (zdalny kontener Module Federation).
6
+ * Użycie: `npx create-hexah-skin <katalog>` (lub `node index.js <katalog>`).
7
+ *
8
+ * Tworzy projekt skórki ze startera (rspack + MF + `@hexah/skin-sdk`) i wypisuje kolejne
9
+ * kroki: `npm run dev` (rspack serve z HMR) oraz jak podłączyć remote do hosta na teście.
10
+ */
11
+
12
+ const fs = require("fs");
13
+ const path = require("path");
14
+ const { toFederationName, copyTemplate } = require("./lib/scaffold");
15
+
16
+ /** @param {string[]} argv @returns {number} kod wyjścia */
17
+ function main(argv) {
18
+ const target = argv[2];
19
+ if (!target) {
20
+ console.error("Użycie: create-hexah-skin <katalog>");
21
+ return 1;
22
+ }
23
+ const destDir = path.resolve(process.cwd(), target);
24
+ if (fs.existsSync(destDir) && fs.readdirSync(destDir).length > 0) {
25
+ console.error(`Katalog „${target}" istnieje i nie jest pusty — przerywam.`);
26
+ return 1;
27
+ }
28
+
29
+ const skinName = path.basename(destDir);
30
+ const federationName = toFederationName(skinName);
31
+ const templateDir = path.join(__dirname, "template");
32
+ copyTemplate(templateDir, destDir, { skinName, federationName });
33
+
34
+ console.log(`✓ Skórka „${skinName}" utworzona w ${target}`);
35
+ console.log("");
36
+ console.log("Dalej:");
37
+ console.log(` cd ${target}`);
38
+ console.log(" npm install");
39
+ console.log(" npm run dev # rspack serve z HMR (np. http://localhost:3001/remoteEntry.js)");
40
+ console.log("");
41
+ console.log("Podłączenie do hosta (środowisko testowe) — w konsoli przeglądarki:");
42
+ console.log(
43
+ ` localStorage.setItem('hexah.skinRemote', JSON.stringify({ name: '${federationName}',`,
44
+ );
45
+ console.log(
46
+ " entry: 'http://localhost:3001/remoteEntry.js', template: 'sdkremote',",
47
+ );
48
+ console.log(" screens: { report: './ReportScreen' }, apiVersion: '0.2.0' }))");
49
+ console.log("Następnie w Ustawieniach Gry wybierz motyw „SDK Remote (zdalna skórka)\".");
50
+ return 0;
51
+ }
52
+
53
+ if (require.main === module) {
54
+ process.exit(main(process.argv));
55
+ }
56
+
57
+ module.exports = { main };
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Czyste funkcje scaffoldu skórki — łatwe do testu, bez efektów sieciowych.
5
+ * CLI (`../index.js`) tylko spina je z argv i FS.
6
+ */
7
+
8
+ const fs = require("fs");
9
+ const path = require("path");
10
+
11
+ /** Pliki template'u zapisane z prefiksem `_`, by nie kolidować z narzędziami repo. */
12
+ const RENAME = Object.freeze({
13
+ "_package.json": "package.json",
14
+ "_gitignore": ".gitignore",
15
+ });
16
+
17
+ /**
18
+ * Sanityzuje nazwę skórki do poprawnej nazwy kontenera Module Federation
19
+ * (identyfikator JS: małe litery, cyfry, `_`; nie zaczyna się od cyfry).
20
+ * @param {string} skinName
21
+ * @returns {string}
22
+ */
23
+ function toFederationName(skinName) {
24
+ const cleaned = String(skinName)
25
+ .trim()
26
+ .toLowerCase()
27
+ .replace(/[^a-z0-9]+/g, "_")
28
+ .replace(/^_+|_+$/g, "");
29
+ const base = cleaned || "skin";
30
+ return /^[0-9]/.test(base) ? `skin_${base}` : base;
31
+ }
32
+
33
+ /**
34
+ * Podstawia placeholdery template'u.
35
+ * @param {string} content
36
+ * @param {{ skinName: string, federationName: string }} vars
37
+ * @returns {string}
38
+ */
39
+ function applyPlaceholders(content, vars) {
40
+ return content
41
+ .replace(/__SKIN_NAME__/g, vars.skinName)
42
+ .replace(/__SKIN_FEDERATION_NAME__/g, vars.federationName);
43
+ }
44
+
45
+ /** @param {string} file @returns {boolean} czy traktować jako tekst (placeholdery). */
46
+ function isTextFile(file) {
47
+ return /\.(js|jsx|json|md|txt)$/.test(file) || file === "_gitignore" || file === "_package.json";
48
+ }
49
+
50
+ /**
51
+ * Kopiuje template do katalogu docelowego, podstawiając placeholdery i zmieniając nazwy
52
+ * z mapy {@link RENAME}. Zwraca listę zapisanych ścieżek.
53
+ * @param {string} srcDir
54
+ * @param {string} destDir
55
+ * @param {{ skinName: string, federationName: string }} vars
56
+ * @returns {string[]}
57
+ */
58
+ function copyTemplate(srcDir, destDir, vars) {
59
+ fs.mkdirSync(destDir, { recursive: true });
60
+ const written = [];
61
+ for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
62
+ const src = path.join(srcDir, entry.name);
63
+ const dest = path.join(destDir, RENAME[entry.name] || entry.name);
64
+ if (entry.isDirectory()) {
65
+ written.push(...copyTemplate(src, dest, vars));
66
+ continue;
67
+ }
68
+ const raw = fs.readFileSync(src, "utf8");
69
+ fs.writeFileSync(dest, isTextFile(entry.name) ? applyPlaceholders(raw, vars) : raw);
70
+ written.push(dest);
71
+ }
72
+ return written;
73
+ }
74
+
75
+ module.exports = { RENAME, toFederationName, applyPlaceholders, isTextFile, copyTemplate };
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@hexah/create-skin",
3
+ "version": "0.1.0",
4
+ "description": "Scaffolder skórki Hexah (zdalny kontener Module Federation, harness dev z HMR).",
5
+ "bin": {
6
+ "create-hexah-skin": "index.js"
7
+ },
8
+ "main": "index.js",
9
+ "license": "MIT",
10
+ "author": "Hexah Team <kontakt@hexah.pl>",
11
+ "homepage": "https://github.com/Kroniki-Fallathanu/hexah/tree/develop/packages/create-hexah-skin#readme",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/Kroniki-Fallathanu/hexah.git",
15
+ "directory": "packages/create-hexah-skin"
16
+ },
17
+ "keywords": [
18
+ "hexah",
19
+ "skin",
20
+ "create",
21
+ "scaffold",
22
+ "module-federation"
23
+ ],
24
+ "engines": {
25
+ "node": ">=20.16.0"
26
+ },
27
+ "files": [
28
+ "index.js",
29
+ "lib",
30
+ "template",
31
+ "LICENSE",
32
+ "README.md"
33
+ ],
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "scripts": {
38
+ "test": "node --test"
39
+ }
40
+ }
@@ -0,0 +1,41 @@
1
+ # __SKIN_NAME__
2
+
3
+ Skórka Hexah zbudowana jako **zdalny kontener Module Federation**. Działa na realnej
4
+ aplikacji Hexah (host) podpiętej do środowiska testowego — bez dostępu do kodu gry.
5
+
6
+ ## Start
7
+
8
+ ```bash
9
+ npm install
10
+ npm run dev # rspack serve z HMR → http://localhost:3001/remoteEntry.js
11
+ ```
12
+
13
+ ## Podłączenie do hosta (środowisko testowe)
14
+
15
+ W konsoli przeglądarki na środowisku testowym Hexah:
16
+
17
+ ```js
18
+ localStorage.setItem('hexah.skinRemote', JSON.stringify({
19
+ name: '__SKIN_FEDERATION_NAME__',
20
+ entry: 'http://localhost:3001/remoteEntry.js',
21
+ template: 'sdkremote',
22
+ screens: { report: './ReportScreen' },
23
+ apiVersion: '0.2.0',
24
+ }))
25
+ ```
26
+
27
+ Odśwież, w **Ustawieniach Gry** wybierz motyw **„SDK Remote (zdalna skórka)"** i wejdź na ekran
28
+ zgłoszeń — zobaczysz swój `ReportScreen` na realnych danych. Dzięki HMR zmiany w `src/` są
29
+ widoczne po odświeżeniu (rebuild remote'a).
30
+
31
+ ## Co możesz edytować
32
+
33
+ - `src/ReportScreen.jsx` — Twój ekran. Importuj wyłącznie z `@hexah/skin-sdk` (+ React/MUI).
34
+ - `rspack.config.js` → `exposes` — dołóż kolejne ekrany (klucze z `SCREEN_KEYS`).
35
+
36
+ ## Zasady
37
+
38
+ - Importuj tylko z `@hexah/skin-sdk` — to publiczny kontrakt (stałe, typy, hooki, UI).
39
+ - Pakiety react/jotai/@mui/@emotion/@hexah/skin-sdk są współdzielone (`import: false`) —
40
+ nie pakujesz ich, bierzesz instancje hosta.
41
+ - Host ładuje skórkę tylko z dozwolonych originów i przy zgodnym `apiVersion`.
@@ -0,0 +1,4 @@
1
+ node_modules
2
+ dist
3
+ .DS_Store
4
+ *.log
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "__SKIN_NAME__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "Skórka Hexah (zdalny kontener Module Federation).",
6
+ "scripts": {
7
+ "build": "rspack build",
8
+ "dev": "rspack serve",
9
+ "serve": "rspack serve"
10
+ },
11
+ "dependencies": {
12
+ "@hexah/skin-sdk": "^0.2.0"
13
+ },
14
+ "devDependencies": {
15
+ "@module-federation/enhanced": "^0.9.0",
16
+ "@rspack/cli": "^1.1.0",
17
+ "@rspack/core": "^1.1.0"
18
+ },
19
+ "peerDependencies": {
20
+ "react": "^19",
21
+ "react-dom": "^19"
22
+ }
23
+ }
@@ -0,0 +1,56 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Build skórki „__SKIN_NAME__" jako zdalnego kontenera Module Federation.
4
+ *
5
+ * Pakiety react/jotai/@mui/@emotion/@hexah/skin-sdk są współdzielone z `import: false` —
6
+ * skórka ich NIE pakuje, konsumuje instancje hosta przez shareScope (jeden React, jeden
7
+ * store Jotai, jeden ThemeProvider). Wynik: `dist/remoteEntry.js`.
8
+ */
9
+ const { ModuleFederationPlugin } = require("@module-federation/enhanced/rspack");
10
+
11
+ const singleton = (requiredVersion) => ({
12
+ singleton: true,
13
+ import: false,
14
+ requiredVersion,
15
+ });
16
+
17
+ module.exports = {
18
+ mode: "production",
19
+ entry: {},
20
+ output: { publicPath: "auto", uniqueName: "__SKIN_FEDERATION_NAME__" },
21
+ resolve: { extensions: [".js", ".jsx"] },
22
+ module: {
23
+ rules: [
24
+ {
25
+ test: /\.jsx?$/,
26
+ use: {
27
+ loader: "builtin:swc-loader",
28
+ options: {
29
+ jsc: {
30
+ parser: { syntax: "ecmascript", jsx: true },
31
+ transform: { react: { runtime: "automatic" } },
32
+ },
33
+ },
34
+ },
35
+ },
36
+ ],
37
+ },
38
+ plugins: [
39
+ new ModuleFederationPlugin({
40
+ name: "__SKIN_FEDERATION_NAME__",
41
+ filename: "remoteEntry.js",
42
+ exposes: { "./ReportScreen": "./src/ReportScreen.jsx" },
43
+ shared: {
44
+ react: singleton("^19"),
45
+ "react-dom": singleton("^19"),
46
+ jotai: singleton("^2"),
47
+ "@emotion/react": singleton("^11"),
48
+ "@emotion/styled": singleton("^11"),
49
+ "@mui/material": singleton("^7"),
50
+ "@mui/material/styles": singleton("^7"),
51
+ "@hexah/skin-sdk": singleton("^0.2"),
52
+ },
53
+ }),
54
+ ],
55
+ devServer: { port: 3001, headers: { "Access-Control-Allow-Origin": "*" } },
56
+ };
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Ekran skórki „__SKIN_NAME__" — importuje WYŁĄCZNIE z `@hexah/skin-sdk` (+ React/MUI jako
3
+ * współdzielone singletony hosta). Host ładuje go w runtime i rejestruje pod `SCREEN_KEYS.REPORT`.
4
+ *
5
+ * Edytuj swobodnie — to Twój punkt startowy. Read-modele (`useReports`…) i prymitywy UI
6
+ * (`PageBox`, `DataList`, `HexahChip`) dostarcza host; tu masz tylko ich kontrakt.
7
+ */
8
+ import { useEffect, useMemo, useState } from "react";
9
+ import Box from "@mui/material/Box";
10
+ import Typography from "@mui/material/Typography";
11
+ import {
12
+ PageBox,
13
+ DataList,
14
+ HexahChip,
15
+ useReports,
16
+ useSnackbar,
17
+ useGamePageShell,
18
+ } from "@hexah/skin-sdk";
19
+
20
+ export default function ReportScreen() {
21
+ const reports = useReports();
22
+ const { notify } = useSnackbar();
23
+ const [issues, setIssues] = useState([]);
24
+ useGamePageShell(useMemo(() => ({ title: "Zgłoszenia (__SKIN_NAME__)" }), []));
25
+
26
+ useEffect(() => {
27
+ reports.list({
28
+ page: 1,
29
+ pageSize: 10,
30
+ callback: (ret) => {
31
+ if (ret?.error) {
32
+ notify([{ severity: "error", message: ret.error }]);
33
+ return;
34
+ }
35
+ setIssues(ret.issues || []);
36
+ },
37
+ });
38
+ }, [reports, notify]);
39
+
40
+ const items = useMemo(
41
+ () =>
42
+ issues.map((issue) => ({
43
+ id: issue.id,
44
+ title: `#${issue.issueNumber} ${issue.title}`,
45
+ chips: (
46
+ <HexahChip
47
+ label={issue.state === "closed" ? "Zamknięte" : "Otwarte"}
48
+ size="small"
49
+ />
50
+ ),
51
+ })),
52
+ [issues],
53
+ );
54
+
55
+ return (
56
+ <PageBox>
57
+ <Box sx={{ px: { xs: 2, sm: 3 }, pt: 2 }}>
58
+ <Typography variant="body2" sx={{ mb: 2, opacity: 0.8 }}>
59
+ Skórka „__SKIN_NAME__" (Module Federation) na realnych danych test backendu hosta.
60
+ </Typography>
61
+ {items.length === 0 ? (
62
+ <Typography color="primary" sx={{ py: 2 }}>
63
+ Brak zgłoszeń do wyświetlenia.
64
+ </Typography>
65
+ ) : (
66
+ <DataList disableListItemAvatar itemsList={items} />
67
+ )}
68
+ </Box>
69
+ </PageBox>
70
+ );
71
+ }