create-lumen 1.1.3 → 1.1.4
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 +2 -2
- package/package.json +8 -2
- package/src/configure.js +23 -13
- package/src/css.js +15 -3
- package/src/dependencies.js +8 -3
- package/src/format.js +28 -0
- package/src/injector.js +15 -4
- package/src/main.js +14 -0
- package/src/readme.js +1 -0
- package/templates/architectures/component-based/main.tsx +1 -1
- package/templates/architectures/component-based/src/App.jsx +0 -1
- package/templates/architectures/component-based/src/App.tsx +0 -1
- package/templates/architectures/component-based/src/components/common/Button.jsx +0 -2
- package/templates/architectures/component-based/src/components/common/Button.tsx +0 -2
- package/templates/architectures/component-based/src/config/constants.js +0 -1
- package/templates/architectures/component-based/src/config/constants.ts +0 -1
- package/templates/architectures/component-based/src/context/AppContext.jsx +3 -4
- package/templates/architectures/component-based/src/context/AppContext.tsx +11 -6
- package/templates/architectures/component-based/src/context/app-context.js +5 -0
- package/templates/architectures/component-based/src/context/app-context.ts +10 -0
- package/templates/architectures/component-based/src/hooks/useLocalStorage.ts +5 -5
- package/templates/architectures/component-based/src/layouts/MainLayout.jsx +0 -2
- package/templates/architectures/component-based/src/layouts/MainLayout.tsx +0 -2
- package/templates/architectures/component-based/src/pages/Home/Home.jsx +0 -1
- package/templates/architectures/component-based/src/pages/Home/Home.tsx +0 -1
- package/templates/architectures/component-based/src/utils/cn.ts +3 -1
- package/templates/architectures/feature-based/main.tsx +1 -1
- package/templates/architectures/feature-based/src/app/App.jsx +0 -1
- package/templates/architectures/feature-based/src/app/App.tsx +0 -1
- package/templates/architectures/feature-based/src/app/providers/ThemeProvider.jsx +0 -2
- package/templates/architectures/feature-based/src/app/providers/ThemeProvider.tsx +2 -2
- package/templates/architectures/feature-based/src/components/ui/Button.jsx +0 -2
- package/templates/architectures/feature-based/src/components/ui/Button.tsx +0 -2
- package/templates/architectures/feature-based/src/config/constants.js +0 -1
- package/templates/architectures/feature-based/src/config/constants.ts +0 -1
- package/templates/architectures/feature-based/src/features/home/pages/HomePage.jsx +0 -1
- package/templates/architectures/feature-based/src/features/home/pages/HomePage.tsx +0 -1
- package/templates/architectures/feature-based/src/hooks/useLocalStorage.ts +5 -5
- package/templates/architectures/feature-based/src/utils/cn.ts +3 -4
- package/templates/conditional/formatter/oxfmt/.oxfmtrc.json +1 -1
- package/templates/conditional/formatter/prettier/.prettierrc +2 -2
- package/templates/conditional/icons/huge/component-based/src/pages/Home/Home.jsx +5 -5
- package/templates/conditional/icons/huge/component-based/src/pages/Home/Home.tsx +5 -5
- package/templates/conditional/icons/huge/feature-based/src/features/home/pages/HomePage.jsx +5 -5
- package/templates/conditional/icons/huge/feature-based/src/features/home/pages/HomePage.tsx +5 -5
- package/templates/conditional/icons/lucide/component-based/src/pages/Home/Home.jsx +2 -3
- package/templates/conditional/icons/lucide/component-based/src/pages/Home/Home.tsx +2 -3
- package/templates/conditional/icons/lucide/feature-based/src/features/home/pages/HomePage.jsx +2 -3
- package/templates/conditional/icons/lucide/feature-based/src/features/home/pages/HomePage.tsx +2 -3
- package/templates/conditional/linting/eslint/eslint.config.js +24 -2
- package/templates/conditional/linting/eslint/eslint.config.ts +25 -2
- package/templates/conditional/router/component-based/src/App.jsx +0 -1
- package/templates/conditional/router/component-based/src/App.tsx +0 -1
- package/templates/conditional/router/component-based/src/routes/index.jsx +0 -1
- package/templates/conditional/router/component-based/src/routes/index.tsx +0 -1
- package/templates/conditional/router/feature-based/src/app/App.jsx +0 -1
- package/templates/conditional/router/feature-based/src/app/App.tsx +0 -1
- package/templates/conditional/state/redux/component-based/src/context/StoreProvider.jsx +0 -1
- package/templates/conditional/state/redux/component-based/src/context/StoreProvider.tsx +2 -2
- package/templates/conditional/state/redux/component-based/src/pages/Home/counterSlice.ts +9 -3
- package/templates/conditional/state/redux/component-based/src/store/hooks.ts +4 -2
- package/templates/conditional/state/redux/component-based/src/store/index.ts +2 -2
- package/templates/conditional/state/redux/feature-based/src/app/providers/StoreProvider.jsx +0 -1
- package/templates/conditional/state/redux/feature-based/src/app/providers/StoreProvider.tsx +2 -2
- package/templates/conditional/state/redux/feature-based/src/features/home/store/counterSlice.ts +9 -3
- package/templates/conditional/state/redux/feature-based/src/stores/hooks.ts +4 -2
- package/templates/conditional/state/redux/feature-based/src/stores/index.ts +2 -2
- package/templates/conditional/state/zustand/component-based/src/store/useCounterStore.ts +8 -1
- package/templates/conditional/state/zustand/feature-based/src/stores/useCounterStore.ts +8 -1
- package/templates/conditional/testing/jest/babel.config.cjs +7 -0
- package/templates/conditional/testing/jest/component-based/src/__tests__/App.test.jsx +10 -0
- package/templates/conditional/testing/jest/component-based/src/__tests__/App.test.tsx +11 -0
- package/templates/conditional/testing/jest/{src → feature-based/src}/__tests__/App.test.jsx +2 -1
- package/templates/conditional/testing/jest/feature-based/src/__tests__/App.test.tsx +11 -0
- package/templates/conditional/testing/jest/jest.config.js +2 -3
- package/templates/conditional/testing/jest/jest.config.ts +6 -4
- package/templates/conditional/testing/jest/jest.setup.js +6 -0
- package/templates/conditional/testing/jest/jest.setup.ts +6 -0
- package/templates/conditional/testing/vitest/component-based/src/__tests__/App.test.jsx +10 -0
- package/templates/conditional/testing/{jest → vitest/component-based}/src/__tests__/App.test.tsx +2 -1
- package/templates/architectures/component-based/src/routes/index.jsx +0 -11
- package/templates/architectures/component-based/src/routes/index.tsx +0 -11
- /package/templates/conditional/testing/vitest/{src → feature-based/src}/__tests__/App.test.jsx +0 -0
- /package/templates/conditional/testing/vitest/{src → feature-based/src}/__tests__/App.test.tsx +0 -0
package/README.md
CHANGED
|
@@ -17,9 +17,9 @@ npm run dev
|
|
|
17
17
|
Non-interactive Quick Setup (TypeScript + Tailwind + Feature-based + Router + ESLint + Prettier + Vitest), no prompts:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
npm create lumen my-app -y
|
|
20
|
+
npm create lumen my-app -- -y
|
|
21
21
|
# or
|
|
22
|
-
npm create lumen -y my-app
|
|
22
|
+
npm create lumen -- -y my-app
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
The CLI ignores flag arguments when resolving the project name, so `-y` can appear before or after the app name without being mistaken for the folder name.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-lumen",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "Scaffold a React + Vite project with architecture choice: feature-based or component-based.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,7 +15,13 @@
|
|
|
15
15
|
"LICENSE"
|
|
16
16
|
],
|
|
17
17
|
"scripts": {
|
|
18
|
-
"test": "node --import ./register.js --test \"tests/unit/*.test.mjs\" \"tests/smoke/*.test.mjs\""
|
|
18
|
+
"test": "node --import ./register.js --test \"tests/unit/*.test.mjs\" \"tests/smoke/*.test.mjs\"",
|
|
19
|
+
"verify:vendor": "node --import ./register.js tests/verify/install-vendor.mjs",
|
|
20
|
+
"verify:offline": "node --import ./register.js tests/verify/verify-offline.mjs",
|
|
21
|
+
"verify:installed": "node --import ./register.js tests/verify/verify-installed.mjs",
|
|
22
|
+
"verify": "npm run verify:vendor && npm run verify:offline",
|
|
23
|
+
"verify:full": "npm run verify:vendor && npm run verify:offline && npm run verify:installed",
|
|
24
|
+
"verify:matrix": "FULL=1 npm run verify:offline"
|
|
19
25
|
},
|
|
20
26
|
"keywords": [
|
|
21
27
|
"scaffold",
|
package/src/configure.js
CHANGED
|
@@ -31,8 +31,13 @@ async function configureTsconfig(projectPath) {
|
|
|
31
31
|
const config = JSON.parse(content);
|
|
32
32
|
|
|
33
33
|
if (!config.compilerOptions) config.compilerOptions = {};
|
|
34
|
-
config.compilerOptions.
|
|
34
|
+
config.compilerOptions.strict = true;
|
|
35
35
|
config.compilerOptions.paths = paths;
|
|
36
|
+
if (config.compilerOptions.types?.length && !config.compilerOptions.types.includes("vite/client")) {
|
|
37
|
+
config.compilerOptions.types.push("vite/client");
|
|
38
|
+
} else if (!config.compilerOptions.types) {
|
|
39
|
+
config.compilerOptions.types = ["vite/client"];
|
|
40
|
+
}
|
|
36
41
|
|
|
37
42
|
await fsp.writeFile(
|
|
38
43
|
tsconfigAppPath,
|
|
@@ -58,7 +63,7 @@ async function configureTsconfig(projectPath) {
|
|
|
58
63
|
noUnusedLocals: true,
|
|
59
64
|
noUnusedParameters: true,
|
|
60
65
|
noFallthroughCasesInSwitch: true,
|
|
61
|
-
|
|
66
|
+
types: ["vite/client"],
|
|
62
67
|
paths,
|
|
63
68
|
},
|
|
64
69
|
include: ["src"],
|
|
@@ -70,11 +75,19 @@ async function configureTsconfig(projectPath) {
|
|
|
70
75
|
);
|
|
71
76
|
}
|
|
72
77
|
|
|
73
|
-
// Create/update root tsconfig.json with references
|
|
78
|
+
// Create/update root tsconfig.json with references.
|
|
79
|
+
// Preserve the ./tsconfig.node.json reference (Vite config) when present.
|
|
74
80
|
const rootTsconfigPath = path.join(projectPath, "tsconfig.json");
|
|
81
|
+
const nodeConfigPath = path.join(projectPath, "tsconfig.node.json");
|
|
82
|
+
const references = [{ path: "./tsconfig.app.json" }];
|
|
83
|
+
try {
|
|
84
|
+
await fsp.access(nodeConfigPath);
|
|
85
|
+
references.push({ path: "./tsconfig.node.json" });
|
|
86
|
+
} catch {}
|
|
87
|
+
|
|
75
88
|
const rootTsconfig = {
|
|
76
89
|
files: [],
|
|
77
|
-
references
|
|
90
|
+
references,
|
|
78
91
|
};
|
|
79
92
|
await fsp.writeFile(
|
|
80
93
|
rootTsconfigPath,
|
|
@@ -100,7 +113,7 @@ async function addPathAliasesToViteConfig(projectPath, ext) {
|
|
|
100
113
|
try {
|
|
101
114
|
let content = await readFileContent(viteConfigPath);
|
|
102
115
|
|
|
103
|
-
if (content.includes("
|
|
116
|
+
if (content.includes("find: '@'")) return;
|
|
104
117
|
|
|
105
118
|
const lines = content.split("\n");
|
|
106
119
|
|
|
@@ -112,8 +125,8 @@ async function addPathAliasesToViteConfig(projectPath, ext) {
|
|
|
112
125
|
|
|
113
126
|
// Insert path/fileURLToPath imports after last import
|
|
114
127
|
const newImports = [
|
|
115
|
-
|
|
116
|
-
|
|
128
|
+
"import path from 'path';",
|
|
129
|
+
"import { fileURLToPath } from 'url';",
|
|
117
130
|
"const __dirname = path.dirname(fileURLToPath(import.meta.url));",
|
|
118
131
|
];
|
|
119
132
|
lines.splice(lastImportIdx + 1, 0, ...newImports);
|
|
@@ -121,13 +134,10 @@ async function addPathAliasesToViteConfig(projectPath, ext) {
|
|
|
121
134
|
let result = lines.join("\n");
|
|
122
135
|
|
|
123
136
|
const aliasArray = getViteAliases()
|
|
124
|
-
.map(
|
|
125
|
-
|
|
126
|
-
`{ find: "${a.find}", replacement: ${a.replacement} }`
|
|
127
|
-
)
|
|
128
|
-
.join(",\n ");
|
|
137
|
+
.map((a) => `{ find: '${a.find}', replacement: ${a.replacement} }`)
|
|
138
|
+
.join(",\n ");
|
|
129
139
|
|
|
130
|
-
const resolveBlock = `resolve: {\n
|
|
140
|
+
const resolveBlock = `resolve: {\n alias: [\n ${aliasArray},\n ],\n }`;
|
|
131
141
|
|
|
132
142
|
if (result.includes("resolve:")) {
|
|
133
143
|
result = result.replace(/resolve:\s*\{/, resolveBlock);
|
package/src/css.js
CHANGED
|
@@ -14,6 +14,18 @@ export async function setupCssFramework({
|
|
|
14
14
|
process.chdir(projectPath);
|
|
15
15
|
const cssDir = path.join(templatesDir, "css");
|
|
16
16
|
const indexCssPath = path.join(projectPath, "src", "index.css");
|
|
17
|
+
// Feature-based main.* imports ./styles/globals.css; component-based main.*
|
|
18
|
+
// imports ./index.css. The chosen framework's styles must land in the file
|
|
19
|
+
// the architecture actually imports.
|
|
20
|
+
const globalsCssPath = path.join(projectPath, "src", "styles", "globals.css");
|
|
21
|
+
|
|
22
|
+
async function writeCss(content) {
|
|
23
|
+
await fsp.writeFile(indexCssPath, content, "utf8");
|
|
24
|
+
try {
|
|
25
|
+
await fsp.access(globalsCssPath);
|
|
26
|
+
await fsp.writeFile(globalsCssPath, content, "utf8");
|
|
27
|
+
} catch {}
|
|
28
|
+
}
|
|
17
29
|
|
|
18
30
|
if (cssFramework === "tailwind") {
|
|
19
31
|
const viteConfigFile = `vite.config.${language === "ts" ? "ts" : "js"}`;
|
|
@@ -28,7 +40,7 @@ export async function setupCssFramework({
|
|
|
28
40
|
path.join(cssDir, "tailwind", "src", "index.css"),
|
|
29
41
|
"utf8"
|
|
30
42
|
);
|
|
31
|
-
await
|
|
43
|
+
await writeCss(indexCssContent);
|
|
32
44
|
} else if (cssFramework === "bootstrap") {
|
|
33
45
|
const mainFile = path.join(projectPath, "src", `main.${ext}`);
|
|
34
46
|
if (fs.existsSync(mainFile)) {
|
|
@@ -45,13 +57,13 @@ export async function setupCssFramework({
|
|
|
45
57
|
path.join(cssDir, "bootstrap", "src", "index.css"),
|
|
46
58
|
"utf8"
|
|
47
59
|
);
|
|
48
|
-
await
|
|
60
|
+
await writeCss(indexCssContent);
|
|
49
61
|
} else {
|
|
50
62
|
const indexCssContent = await fsp.readFile(
|
|
51
63
|
path.join(cssDir, "none", "src", "index.css"),
|
|
52
64
|
"utf8"
|
|
53
65
|
);
|
|
54
|
-
await
|
|
66
|
+
await writeCss(indexCssContent);
|
|
55
67
|
}
|
|
56
68
|
|
|
57
69
|
// Clean up Vite's default App.css
|
package/src/dependencies.js
CHANGED
|
@@ -71,11 +71,14 @@ export async function installAllDeps(responses, pkg, cwd) {
|
|
|
71
71
|
} else if (responses.testing === "jest") {
|
|
72
72
|
devDeps.push(
|
|
73
73
|
"jest",
|
|
74
|
-
"
|
|
75
|
-
"@types/jest",
|
|
74
|
+
"jest-environment-jsdom",
|
|
76
75
|
"@testing-library/react",
|
|
77
76
|
"@testing-library/jest-dom",
|
|
78
|
-
"jsdom"
|
|
77
|
+
"jsdom",
|
|
78
|
+
"babel-jest",
|
|
79
|
+
"@babel/preset-env",
|
|
80
|
+
"@babel/preset-react",
|
|
81
|
+
"@babel/preset-typescript"
|
|
79
82
|
);
|
|
80
83
|
}
|
|
81
84
|
|
|
@@ -84,6 +87,8 @@ export async function installAllDeps(responses, pkg, cwd) {
|
|
|
84
87
|
devDeps.push("eslint", "@eslint/js", "eslint-plugin-react-hooks", "eslint-plugin-react-refresh", "globals");
|
|
85
88
|
if (responses.language === "ts") {
|
|
86
89
|
devDeps.push("typescript-eslint");
|
|
90
|
+
// ESLint >= 10 loads TS config files (eslint.config.ts) via jiti.
|
|
91
|
+
devDeps.push("jiti");
|
|
87
92
|
}
|
|
88
93
|
} else if (responses.linter === "oxlint") {
|
|
89
94
|
devDeps.push("oxlint");
|
package/src/format.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { execa } from "execa";
|
|
3
|
+
import { promises as fsp } from "fs";
|
|
4
|
+
|
|
5
|
+
async function exists(p) {
|
|
6
|
+
try {
|
|
7
|
+
await fsp.access(p);
|
|
8
|
+
return true;
|
|
9
|
+
} catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Runs the project-local formatter (installed as a devDependency) so a freshly
|
|
15
|
+
// generated project is canonical under its chosen formatter by construction.
|
|
16
|
+
// Falls back to the bare command name so the same helper works when the binary
|
|
17
|
+
// is already on PATH (e.g. verification harnesses resolving from a vendored
|
|
18
|
+
// node_modules symlink).
|
|
19
|
+
export async function runProjectFormat(projectPath, responses) {
|
|
20
|
+
if (!responses.formatter || responses.formatter === "none") return;
|
|
21
|
+
|
|
22
|
+
const formatter = responses.formatter;
|
|
23
|
+
const bin = path.join(projectPath, "node_modules", ".bin", formatter);
|
|
24
|
+
const command = (await exists(bin)) ? bin : formatter;
|
|
25
|
+
const args = formatter === "prettier" ? ["--write", "."] : ["."];
|
|
26
|
+
|
|
27
|
+
await execa(command, args, { cwd: projectPath, stdio: "pipe" });
|
|
28
|
+
}
|
package/src/injector.js
CHANGED
|
@@ -72,6 +72,7 @@ export async function injectFormatter(projectPath, templatesDir, responses) {
|
|
|
72
72
|
pkg.scripts["format:check"] = "prettier --check .";
|
|
73
73
|
} else if (responses.formatter === "oxfmt") {
|
|
74
74
|
pkg.scripts.format = "oxfmt .";
|
|
75
|
+
pkg.scripts["format:check"] = "oxfmt --check .";
|
|
75
76
|
}
|
|
76
77
|
await fsp.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
77
78
|
} catch {}
|
|
@@ -105,11 +106,14 @@ async function wireEslintPrettier(projectPath, language) {
|
|
|
105
106
|
|
|
106
107
|
let result = lines.join("\n");
|
|
107
108
|
|
|
108
|
-
// Append
|
|
109
|
+
// Append prettier as the LAST element of the eslint config.
|
|
110
|
+
// eslint-config-prettier exports a flat-config object (not an iterable),
|
|
111
|
+
// so it must be passed by reference, not spread — passing `...prettier`
|
|
112
|
+
// breaks under ESLint's jiti-based TS config loading.
|
|
109
113
|
// The JS template exports an array (`export default [...]`); the TS
|
|
110
114
|
// template uses a function call (`export default tseslint.config(...)`).
|
|
111
115
|
const pushLast = (_, head, tail) =>
|
|
112
|
-
head.replace(/,\s*$/, "") + ",\n
|
|
116
|
+
head.replace(/,\s*$/, "") + ",\n prettier," + tail;
|
|
113
117
|
if (result.includes("tseslint.config(")) {
|
|
114
118
|
result = result.replace(
|
|
115
119
|
/(export default tseslint\.config\([\s\S]*?)(\n\s*\);)/,
|
|
@@ -247,11 +251,18 @@ async function injectTesting(projectPath, templatesDir, framework, language, arc
|
|
|
247
251
|
await fsp.access(jestSetupSrc);
|
|
248
252
|
await fsp.copyFile(jestSetupSrc, path.join(projectPath, `jest.setup.${configExt}`));
|
|
249
253
|
} catch {}
|
|
254
|
+
|
|
255
|
+
// Unified Babel transform config for Jest (env + react + typescript presets).
|
|
256
|
+
const babelSrc = path.join(testingDir, "babel.config.cjs");
|
|
257
|
+
try {
|
|
258
|
+
await fsp.access(babelSrc);
|
|
259
|
+
await fsp.copyFile(babelSrc, path.join(projectPath, "babel.config.cjs"));
|
|
260
|
+
} catch {}
|
|
250
261
|
}
|
|
251
262
|
|
|
252
|
-
// Copy test file
|
|
263
|
+
// Copy test file (architecture-scoped so the App import path resolves)
|
|
253
264
|
const testExt = language === "ts" ? "tsx" : "jsx";
|
|
254
|
-
const testSrc = path.join(testingDir, `src/__tests__/App.test.${testExt}`);
|
|
265
|
+
const testSrc = path.join(testingDir, architecture, `src/__tests__/App.test.${testExt}`);
|
|
255
266
|
try {
|
|
256
267
|
await fsp.access(testSrc);
|
|
257
268
|
const testDest = path.join(projectPath, "src", "__tests__", `App.test.${testExt}`);
|
package/src/main.js
CHANGED
|
@@ -15,6 +15,7 @@ import { getUserInputs, onCancel } from "@/prompts.js";
|
|
|
15
15
|
import { runBaseInstall, runGitInit, runViteCreate } from "@/scaffold.js";
|
|
16
16
|
import { installAllDeps } from "@/dependencies.js";
|
|
17
17
|
import { generateReadme } from "@/readme.js";
|
|
18
|
+
import { runProjectFormat } from "@/format.js";
|
|
18
19
|
|
|
19
20
|
const __filename = fileURLToPath(import.meta.url);
|
|
20
21
|
const __dirname = path.dirname(__filename);
|
|
@@ -215,6 +216,19 @@ export async function main(options = {}) {
|
|
|
215
216
|
}
|
|
216
217
|
}
|
|
217
218
|
|
|
219
|
+
// 13.5 Format the freshly generated project so it is canonical on init
|
|
220
|
+
if (responses.formatter && responses.formatter !== "none") {
|
|
221
|
+
const fmtPassSpin = spinner();
|
|
222
|
+
fmtPassSpin.start(`Formatting project with ${responses.formatter}...`);
|
|
223
|
+
try {
|
|
224
|
+
await runProjectFormat(projectPath, responses);
|
|
225
|
+
fmtPassSpin.stop(chalk.green("Project formatted."));
|
|
226
|
+
} catch (err) {
|
|
227
|
+
fmtPassSpin.stop(chalk.red("Failed to format project."));
|
|
228
|
+
throw err;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
218
232
|
log.step(chalk.green("\nProject setup complete!"));
|
|
219
233
|
log.message(
|
|
220
234
|
chalk.gray(
|
package/src/readme.js
CHANGED
|
@@ -77,6 +77,7 @@ function buildScripts(responses) {
|
|
|
77
77
|
|
|
78
78
|
if (responses.formatter === "prettier" || responses.formatter === "oxfmt") {
|
|
79
79
|
rows.push("| `npm run format` | Format code |");
|
|
80
|
+
rows.push("| `npm run format:check` | Verify code is formatted |");
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
return rows.join("\n");
|
|
@@ -3,7 +3,7 @@ import ReactDOM from "react-dom/client";
|
|
|
3
3
|
import App from "./App";
|
|
4
4
|
import "./styles/globals.css";
|
|
5
5
|
|
|
6
|
-
ReactDOM.createRoot(document.getElementById("root")).render(
|
|
6
|
+
ReactDOM.createRoot(document.getElementById("root")!).render(
|
|
7
7
|
<React.StrictMode>
|
|
8
8
|
<App />
|
|
9
9
|
</React.StrictMode>
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
export const AppContext = createContext(null);
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { AppContext } from "./app-context";
|
|
4
3
|
|
|
5
4
|
export function AppProvider({ children }) {
|
|
6
5
|
const [theme, setTheme] = useState("light");
|
|
@@ -10,4 +9,4 @@ export function AppProvider({ children }) {
|
|
|
10
9
|
{children}
|
|
11
10
|
</AppContext.Provider>
|
|
12
11
|
);
|
|
13
|
-
}
|
|
12
|
+
}
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useState, useEffect, type ReactNode } from "react";
|
|
2
|
+
import { AppContext, type AppContextValue } from "./app-context";
|
|
2
3
|
|
|
3
|
-
export
|
|
4
|
+
export function AppProvider({ children }: { children: ReactNode }) {
|
|
5
|
+
const [theme, setTheme] = useState(() => localStorage.getItem("theme") ?? "light");
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
localStorage.setItem("theme", theme);
|
|
9
|
+
}, [theme]);
|
|
10
|
+
|
|
11
|
+
const value: AppContextValue = { theme, setTheme };
|
|
7
12
|
|
|
8
13
|
return (
|
|
9
|
-
<AppContext.Provider value={
|
|
14
|
+
<AppContext.Provider value={value}>
|
|
10
15
|
{children}
|
|
11
16
|
</AppContext.Provider>
|
|
12
17
|
);
|
|
13
|
-
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createContext } from "react";
|
|
2
|
+
|
|
3
|
+
// React Fast Refresh requires contexts to live in a separate file from
|
|
4
|
+
// components, so the AppProvider component exports from AppContext.tsx.
|
|
5
|
+
export interface AppContextValue {
|
|
6
|
+
theme: string;
|
|
7
|
+
setTheme: (theme: string) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const AppContext = createContext<AppContextValue | null>(null);
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { useState, useCallback } from "react";
|
|
2
2
|
|
|
3
|
-
export function useLocalStorage(key, initialValue) {
|
|
4
|
-
const [storedValue, setStoredValue] = useState(() => {
|
|
3
|
+
export function useLocalStorage<T>(key: string, initialValue: T) {
|
|
4
|
+
const [storedValue, setStoredValue] = useState<T>(() => {
|
|
5
5
|
try {
|
|
6
6
|
const item = window.localStorage.getItem(key);
|
|
7
|
-
return item ? JSON.parse(item) : initialValue;
|
|
7
|
+
return item ? (JSON.parse(item) as T) : initialValue;
|
|
8
8
|
} catch {
|
|
9
9
|
return initialValue;
|
|
10
10
|
}
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
const setValue = useCallback(
|
|
14
|
-
(value) => {
|
|
14
|
+
(value: T | ((prev: T) => T)) => {
|
|
15
15
|
try {
|
|
16
16
|
const valueToStore =
|
|
17
17
|
value instanceof Function ? value(storedValue) : value;
|
|
@@ -24,5 +24,5 @@ export function useLocalStorage(key, initialValue) {
|
|
|
24
24
|
[key, storedValue]
|
|
25
25
|
);
|
|
26
26
|
|
|
27
|
-
return [storedValue, setValue];
|
|
27
|
+
return [storedValue, setValue] as const;
|
|
28
28
|
}
|
|
@@ -3,7 +3,7 @@ import ReactDOM from "react-dom/client";
|
|
|
3
3
|
import App from "@/app/App";
|
|
4
4
|
import "./styles/globals.css";
|
|
5
5
|
|
|
6
|
-
ReactDOM.createRoot(document.getElementById("root")).render(
|
|
6
|
+
ReactDOM.createRoot(document.getElementById("root")!).render(
|
|
7
7
|
<React.StrictMode>
|
|
8
8
|
<App />
|
|
9
9
|
</React.StrictMode>
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { useState, useCallback } from "react";
|
|
2
2
|
|
|
3
|
-
export function useLocalStorage(key, initialValue) {
|
|
4
|
-
const [storedValue, setStoredValue] = useState(() => {
|
|
3
|
+
export function useLocalStorage<T>(key: string, initialValue: T) {
|
|
4
|
+
const [storedValue, setStoredValue] = useState<T>(() => {
|
|
5
5
|
try {
|
|
6
6
|
const item = window.localStorage.getItem(key);
|
|
7
|
-
return item ? JSON.parse(item) : initialValue;
|
|
7
|
+
return item ? (JSON.parse(item) as T) : initialValue;
|
|
8
8
|
} catch {
|
|
9
9
|
return initialValue;
|
|
10
10
|
}
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
const setValue = useCallback(
|
|
14
|
-
(value) => {
|
|
14
|
+
(value: T | ((prev: T) => T)) => {
|
|
15
15
|
try {
|
|
16
16
|
const valueToStore =
|
|
17
17
|
value instanceof Function ? value(storedValue) : value;
|
|
@@ -24,5 +24,5 @@ export function useLocalStorage(key, initialValue) {
|
|
|
24
24
|
[key, storedValue]
|
|
25
25
|
);
|
|
26
26
|
|
|
27
|
-
return [storedValue, setValue];
|
|
27
|
+
return [storedValue, setValue] as const;
|
|
28
28
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
import { twMerge } from "tailwind-merge";
|
|
1
|
+
type ClassValue = string | false | null | undefined;
|
|
3
2
|
|
|
4
|
-
export function cn(...inputs) {
|
|
5
|
-
return
|
|
3
|
+
export function cn(...inputs: ClassValue[]): string {
|
|
4
|
+
return inputs.filter(Boolean).join(" ");
|
|
6
5
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { HugeiconsIcon } from "@hugeicons/react";
|
|
2
|
+
import { Sun01Icon, Moon01Icon, Github01Icon } from "@hugeicons/core-free-icons";
|
|
3
3
|
import { Button } from "@/components/common";
|
|
4
4
|
|
|
5
5
|
export function HomePage() {
|
|
6
6
|
return (
|
|
7
7
|
<div className="text-center">
|
|
8
8
|
<div className="flex justify-center gap-4 mb-6">
|
|
9
|
-
<
|
|
10
|
-
<
|
|
11
|
-
<
|
|
9
|
+
<HugeiconsIcon icon={Sun01Icon} className="w-8 h-8 text-yellow-500" />
|
|
10
|
+
<HugeiconsIcon icon={Moon01Icon} className="w-8 h-8 text-blue-500" />
|
|
11
|
+
<HugeiconsIcon icon={Github01Icon} className="w-8 h-8 text-gray-800" />
|
|
12
12
|
</div>
|
|
13
13
|
<h1 className="text-4xl font-bold text-gray-900 mb-4">Welcome to Lumen</h1>
|
|
14
14
|
<p className="text-gray-600 mb-8">
|