create-lumen 2.0.0-beta.2 → 2.0.0-beta.3

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.
Files changed (56) hide show
  1. package/README.md +23 -22
  2. package/bin/cli.js +1 -6
  3. package/package.json +2 -2
  4. package/schema/fixtures/v2/valid/next-app-router-feature-based.json +1 -1
  5. package/schema/fixtures/v2/valid/next-app-router-none.json +1 -1
  6. package/schema/fixtures/v2/valid/next-pages-router-feature-based.json +1 -1
  7. package/schema/fixtures/v2/valid/next-pages-router-none.json +1 -1
  8. package/schema/lumen.config.v2.json +2 -3
  9. package/src/cleanup.js +20 -5
  10. package/src/cli-args.js +53 -2
  11. package/src/configure.js +36 -8
  12. package/src/css.js +3 -4
  13. package/src/dependencies.js +2 -2
  14. package/src/engine/capabilities.js +2 -2
  15. package/src/engine/composition.js +44 -10
  16. package/src/env.js +149 -12
  17. package/src/format.js +14 -6
  18. package/src/injector.js +66 -14
  19. package/src/main.js +57 -12
  20. package/src/manifest/emit.js +23 -7
  21. package/src/manifest/paths.js +1 -25
  22. package/src/manifest/schema.js +3 -3
  23. package/src/prompts.js +25 -8
  24. package/src/readme.js +0 -16
  25. package/src/utils/dev-server.js +6 -1
  26. package/templates/.env.example +1 -1
  27. package/templates/architectures/feature-based/scripts/create-feature.mjs +2 -2
  28. package/templates/conditional/linting/biome/biome.json +17 -11
  29. package/templates/conditional/linting/eslint/next/eslint.config.mjs +2 -11
  30. package/templates/conditional/testing/vitest/next/vitest.config.js +1 -1
  31. package/templates/conditional/testing/vitest/next/vitest.config.ts +1 -1
  32. package/templates/css/tailwind/postcss.config.mjs +3 -1
  33. package/schema/fixtures/v2/valid/next-app-router-hybrid.json +0 -35
  34. package/schema/fixtures/v2/valid/next-pages-router-hybrid.json +0 -35
  35. package/templates/architectures/hybrid/main.jsx +0 -10
  36. package/templates/architectures/hybrid/main.tsx +0 -10
  37. package/templates/architectures/hybrid/src/App.jsx +0 -20
  38. package/templates/architectures/hybrid/src/App.tsx +0 -20
  39. package/templates/architectures/hybrid/src/features/.gitkeep +0 -1
  40. package/templates/architectures/hybrid/src/shared/components/ui/Button.jsx +0 -31
  41. package/templates/architectures/hybrid/src/shared/components/ui/Button.tsx +0 -36
  42. package/templates/architectures/hybrid/src/shared/components/ui/index.js +0 -1
  43. package/templates/architectures/hybrid/src/shared/components/ui/index.ts +0 -1
  44. package/templates/architectures/hybrid/src/shared/hooks/useLocalStorage.jsx +0 -28
  45. package/templates/architectures/hybrid/src/shared/hooks/useLocalStorage.ts +0 -28
  46. package/templates/architectures/hybrid/src/shared/layouts/.gitkeep +0 -0
  47. package/templates/architectures/hybrid/src/shared/stores/.gitkeep +0 -0
  48. package/templates/architectures/hybrid/src/shared/styles/.gitkeep +0 -0
  49. package/templates/architectures/hybrid/src/shared/types/index.js +0 -20
  50. package/templates/architectures/hybrid/src/shared/types/index.ts +0 -16
  51. package/templates/architectures/hybrid/src/shared/utils/cn.jsx +0 -3
  52. package/templates/architectures/hybrid/src/shared/utils/cn.ts +0 -5
  53. package/templates/conditional/testing/jest/hybrid/src/__tests__/App.test.jsx +0 -11
  54. package/templates/conditional/testing/jest/hybrid/src/__tests__/App.test.tsx +0 -11
  55. package/templates/conditional/testing/vitest/hybrid/src/__tests__/App.test.jsx +0 -10
  56. package/templates/conditional/testing/vitest/hybrid/src/__tests__/App.test.tsx +0 -10
package/src/prompts.js CHANGED
@@ -24,8 +24,6 @@ export function formatConfig(responses) {
24
24
  ? "Feature-based"
25
25
  : responses.architecture === "type-based" || responses.architecture === "component-based"
26
26
  ? "Type-based"
27
- : responses.architecture === "hybrid"
28
- ? "Hybrid (shared + features)"
29
27
  : responses.architecture === "none"
30
28
  ? "None (flat src)"
31
29
  : responses.architecture;
@@ -72,7 +70,8 @@ export function formatConfig(responses) {
72
70
  lines.push(`• ${chalk.bold("Adapter:")} ${chalk.cyan(responses.adapter)}`);
73
71
  }
74
72
  if (responses.database && responses.database !== "none") {
75
- lines.push(`• ${chalk.bold("Database:")} ${chalk.cyan(responses.database === "prisma" ? "Prisma ORM" : responses.database)}`);
73
+ const providerLabel = responses.databaseProvider === "supabase" ? " (Supabase)" : " (PostgreSQL)";
74
+ lines.push(`• ${chalk.bold("Database:")} ${chalk.cyan(responses.database === "prisma" ? `Prisma ORM${providerLabel}` : responses.database)}`);
76
75
  }
77
76
  if (responses.reactCompiler !== undefined) {
78
77
  lines.push(`• ${chalk.bold("React Compiler:")} ${responses.reactCompiler ? chalk.green("Yes") : chalk.red("No")}`);
@@ -360,11 +359,6 @@ export async function getUserInputs(
360
359
  value: "type-based",
361
360
  hint: "Small apps / component libraries — code grouped by UI type",
362
361
  },
363
- {
364
- label: "Hybrid",
365
- value: "hybrid",
366
- hint: "Next.js: features + shared code",
367
- },
368
362
  {
369
363
  label: "None",
370
364
  value: "none",
@@ -491,6 +485,27 @@ export async function getUserInputs(
491
485
  ]),
492
486
  defaultInitial: "none",
493
487
  },
488
+ {
489
+ id: "databaseProvider",
490
+ skip: (ans) => !isPromptVisible(framework, "database") || ans.database !== "prisma",
491
+ onSkip: (ans) => {
492
+ ans.databaseProvider = ans.database === "prisma" ? (ans.databaseProvider || "postgresql") : undefined;
493
+ },
494
+ message: "Which database provider do you want to configure?",
495
+ getOptions: () => [
496
+ {
497
+ label: "PostgreSQL",
498
+ value: "postgresql",
499
+ hint: "Standard PostgreSQL / Docker compose",
500
+ },
501
+ {
502
+ label: "Supabase",
503
+ value: "supabase",
504
+ hint: "PostgreSQL with connection pooling & direct migration URL",
505
+ },
506
+ ],
507
+ defaultInitial: "postgresql",
508
+ },
494
509
  {
495
510
  id: "reactCompiler",
496
511
  skip: () => !isPromptVisible(framework, "reactCompiler"),
@@ -682,6 +697,7 @@ export async function getUserInputs(
682
697
  bundler,
683
698
  adapter,
684
699
  database,
700
+ databaseProvider,
685
701
  reactCompiler,
686
702
  routeGroups,
687
703
  agentDocs,
@@ -704,6 +720,7 @@ export async function getUserInputs(
704
720
  }
705
721
  if (database !== undefined && database !== "none") {
706
722
  finalResponses.database = database;
723
+ finalResponses.databaseProvider = databaseProvider || "postgresql";
707
724
  }
708
725
  if (reactCompiler !== undefined) {
709
726
  finalResponses.reactCompiler = reactCompiler;
package/src/readme.js CHANGED
@@ -117,22 +117,6 @@ ${nestedTree(appChildren)}
117
117
  ${nestedTree(shared)}`;
118
118
  }
119
119
 
120
- if (responses.architecture === "hybrid") {
121
- const shared = [
122
- "components/ # Shared UI components",
123
- "hooks/ # Shared custom hooks",
124
- "layouts/ # High-level layout wrappers",
125
- "stores/ # Global state stores",
126
- `styles/ # ${responses.cssFramework === "none" ? "main.css (reset) + themes.css (CSS variables)" : "globals.css (framework directives) + themes.css"}`,
127
- "types/ # Global type definitions",
128
- "utils/ # Pure utility functions",
129
- ];
130
- return `src/
131
- ├── features/ # Feature modules (domain boundaries)
132
- └── shared/ # Reusable, business-agnostic resources
133
- ${nestedTree(shared)}`;
134
- }
135
-
136
120
  if (responses.architecture === "none") {
137
121
  return `src/
138
122
  ├── App.${responses.language === "ts" ? "tsx" : "jsx"} # Root application component
@@ -2,7 +2,12 @@ import { isCancel, select } from "@clack/prompts";
2
2
  import chalk from "chalk";
3
3
  import { execa } from "execa";
4
4
 
5
- export async function handleDevServer(pkg, projectName) {
5
+ export async function handleDevServer(pkg, projectName, { isNonInteractive = false } = {}) {
6
+ if (isNonInteractive || process.env.CI || !process.stdin.isTTY) {
7
+ printNextSteps(projectName, pkg);
8
+ return;
9
+ }
10
+
6
11
  const startDev = await select({
7
12
  message: "Start the development server?",
8
13
  options: [
@@ -1,4 +1,4 @@
1
1
  # Copy this file to `.env.local` (or `.env`) and fill in your values.
2
- # See https://vitejs.dev/guide/env-and-mode for how env vars are exposed.
2
+ # See https://vite.dev/guide/env-and-mode for how env vars are exposed.
3
3
 
4
4
  VITE_API_URL=https://api.example.com
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import fs from "fs";
4
- import path from "path";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
5
 
6
6
  // 1. Get feature name from CLI args
7
7
  const featureName = process.argv[2];
@@ -1,31 +1,37 @@
1
1
  {
2
- "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
3
- "vcs": {
4
- "enabled": false,
5
- "clientKind": "git",
6
- "useIgnoreFile": true
7
- },
2
+ "$schema": "https://biomejs.dev/schemas/2.6.3/schema.json",
8
3
  "files": {
9
4
  "ignoreUnknown": false,
10
- "ignore": []
5
+ "includes": ["**", "!**/.next", "!**/dist", "!**/node_modules"]
11
6
  },
12
7
  "formatter": {
13
8
  "enabled": true,
14
9
  "indentStyle": "space",
15
10
  "indentWidth": 2
16
11
  },
17
- "organizeImports": {
18
- "enabled": true
19
- },
20
12
  "linter": {
21
13
  "enabled": true,
22
14
  "rules": {
23
- "recommended": true
15
+ "preset": "recommended"
24
16
  }
25
17
  },
26
18
  "javascript": {
27
19
  "formatter": {
28
20
  "quoteStyle": "double"
29
21
  }
22
+ },
23
+ "css": {
24
+ "parser": {
25
+ "tailwindDirectives": true
26
+ }
27
+ },
28
+ "assist": {
29
+ "enabled": true,
30
+ "actions": {
31
+ "source": {
32
+ "organizeImports": "on"
33
+ }
34
+ }
30
35
  }
31
36
  }
37
+
@@ -1,16 +1,7 @@
1
- import { dirname } from "path";
2
- import { fileURLToPath } from "url";
3
- import { FlatCompat } from "@eslint/eslintrc";
4
-
5
- const __filename = fileURLToPath(import.meta.url);
6
- const __dirname = dirname(__filename);
7
-
8
- const compat = new FlatCompat({
9
- baseDirectory: __dirname,
10
- });
1
+ import nextConfig from "eslint-config-next";
11
2
 
12
3
  const eslintConfig = [
13
- ...compat.extends("next/core-web-vitals"),
4
+ ...nextConfig,
14
5
  ];
15
6
 
16
7
  export default eslintConfig;
@@ -1,5 +1,5 @@
1
+ import path from "node:path";
1
2
  import { defineConfig } from "vitest/config";
2
- import path from "path";
3
3
 
4
4
  export default defineConfig({
5
5
  resolve: {
@@ -1,5 +1,5 @@
1
+ import path from "node:path";
1
2
  import { defineConfig } from "vitest/config";
2
- import path from "path";
3
3
 
4
4
  export default defineConfig({
5
5
  resolve: {
@@ -1,5 +1,7 @@
1
- export default {
1
+ const config = {
2
2
  plugins: {
3
3
  "@tailwindcss/postcss": {},
4
4
  },
5
5
  };
6
+
7
+ export default config;
@@ -1,35 +0,0 @@
1
- {
2
- "$schema": "https://lumen-sh.github.io/create-lumen/schema/lumen.config.v2.json",
3
- "manifestVersion": 2,
4
- "framework": {
5
- "name": "next",
6
- "variant": "app-router",
7
- "bundler": "turbopack",
8
- "adapter": "node"
9
- },
10
- "styling": {
11
- "engine": "tailwind"
12
- },
13
- "architecture": {
14
- "type": "hybrid"
15
- },
16
- "ui": {
17
- "kit": "none"
18
- },
19
- "docs": {
20
- "language": "en"
21
- },
22
- "paths": {
23
- "features": "src/features",
24
- "components": "src/shared/components",
25
- "services": "src/shared/services",
26
- "hooks": "src/shared/hooks",
27
- "pages": "app",
28
- "ui": "src/shared/components/ui"
29
- },
30
- "tooling": {
31
- "language": "ts",
32
- "linter": "eslint",
33
- "formatter": "prettier"
34
- }
35
- }
@@ -1,35 +0,0 @@
1
- {
2
- "$schema": "https://lumen-sh.github.io/create-lumen/schema/lumen.config.v2.json",
3
- "manifestVersion": 2,
4
- "framework": {
5
- "name": "next",
6
- "variant": "pages-router",
7
- "bundler": "turbopack",
8
- "adapter": "static"
9
- },
10
- "styling": {
11
- "engine": "tailwind"
12
- },
13
- "architecture": {
14
- "type": "hybrid"
15
- },
16
- "ui": {
17
- "kit": "none"
18
- },
19
- "docs": {
20
- "language": "es"
21
- },
22
- "paths": {
23
- "features": "src/features",
24
- "components": "src/shared/components",
25
- "services": "src/shared/services",
26
- "hooks": "src/shared/hooks",
27
- "pages": "pages",
28
- "ui": "src/shared/components/ui"
29
- },
30
- "tooling": {
31
- "language": "js",
32
- "linter": "eslint",
33
- "formatter": "prettier"
34
- }
35
- }
@@ -1,10 +0,0 @@
1
- import React from "react";
2
- import ReactDOM from "react-dom/client";
3
- import App from "./App";
4
- import "./shared/styles/globals.css";
5
-
6
- ReactDOM.createRoot(document.getElementById("root")).render(
7
- <React.StrictMode>
8
- <App />
9
- </React.StrictMode>
10
- );
@@ -1,10 +0,0 @@
1
- import React from "react";
2
- import ReactDOM from "react-dom/client";
3
- import App from "./App";
4
- import "./shared/styles/globals.css";
5
-
6
- ReactDOM.createRoot(document.getElementById("root")!).render(
7
- <React.StrictMode>
8
- <App />
9
- </React.StrictMode>
10
- );
@@ -1,20 +0,0 @@
1
- import { Button } from "@/shared/components/ui/Button";
2
-
3
- export default function App() {
4
- return (
5
- <div
6
- style={{
7
- minHeight: "100vh",
8
- display: "flex",
9
- flexDirection: "column",
10
- alignItems: "center",
11
- justifyContent: "center",
12
- backgroundColor: "var(--color-bg, #ffffff)",
13
- }}
14
- >
15
- <h1>Welcome to Lumen</h1>
16
- <p>Hybrid architecture (src/features + src/shared).</p>
17
- <Button>Click me</Button>
18
- </div>
19
- );
20
- }
@@ -1,20 +0,0 @@
1
- import { Button } from "@/shared/components/ui/Button";
2
-
3
- export default function App() {
4
- return (
5
- <div
6
- style={{
7
- minHeight: "100vh",
8
- display: "flex",
9
- flexDirection: "column",
10
- alignItems: "center",
11
- justifyContent: "center",
12
- backgroundColor: "var(--color-bg, #ffffff)",
13
- }}
14
- >
15
- <h1>Welcome to Lumen</h1>
16
- <p>Hybrid architecture (src/features + src/shared).</p>
17
- <Button>Click me</Button>
18
- </div>
19
- );
20
- }
@@ -1,31 +0,0 @@
1
- export function Button({
2
- variant = "primary",
3
- children,
4
- className,
5
- ...props
6
- }) {
7
- const styles = {
8
- primary: { backgroundColor: "var(--color-primary)", color: "white" },
9
- secondary: {
10
- backgroundColor: "var(--color-border)",
11
- color: "var(--color-fg)",
12
- },
13
- };
14
-
15
- return (
16
- <button
17
- style={{
18
- padding: "8px 16px",
19
- borderRadius: "8px",
20
- fontWeight: 500,
21
- border: "none",
22
- cursor: "pointer",
23
- ...styles[variant],
24
- }}
25
- className={className}
26
- {...props}
27
- >
28
- {children}
29
- </button>
30
- );
31
- }
@@ -1,36 +0,0 @@
1
- interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
2
- variant?: "primary" | "secondary";
3
- children: React.ReactNode;
4
- }
5
-
6
- export function Button({
7
- variant = "primary",
8
- children,
9
- className,
10
- ...props
11
- }: ButtonProps) {
12
- const styles = {
13
- primary: { backgroundColor: "var(--color-primary)", color: "white" },
14
- secondary: {
15
- backgroundColor: "var(--color-border)",
16
- color: "var(--color-fg)",
17
- },
18
- };
19
-
20
- return (
21
- <button
22
- style={{
23
- padding: "8px 16px",
24
- borderRadius: "8px",
25
- fontWeight: 500,
26
- border: "none",
27
- cursor: "pointer",
28
- ...styles[variant],
29
- }}
30
- className={className}
31
- {...props}
32
- >
33
- {children}
34
- </button>
35
- );
36
- }
@@ -1 +0,0 @@
1
- export { Button } from "./Button";
@@ -1 +0,0 @@
1
- export { Button } from "./Button";
@@ -1,28 +0,0 @@
1
- import { useState, useCallback } from "react";
2
-
3
- export function useLocalStorage(key, initialValue) {
4
- const [storedValue, setStoredValue] = useState(() => {
5
- try {
6
- const item = window.localStorage.getItem(key);
7
- return item ? JSON.parse(item) : initialValue;
8
- } catch {
9
- return initialValue;
10
- }
11
- });
12
-
13
- const setValue = useCallback(
14
- (value) => {
15
- try {
16
- const valueToStore =
17
- value instanceof Function ? value(storedValue) : value;
18
- setStoredValue(valueToStore);
19
- window.localStorage.setItem(key, JSON.stringify(valueToStore));
20
- } catch (error) {
21
- console.error(error);
22
- }
23
- },
24
- [key, storedValue]
25
- );
26
-
27
- return [storedValue, setValue];
28
- }
@@ -1,28 +0,0 @@
1
- import { useState, useCallback } from "react";
2
-
3
- export function useLocalStorage<T>(key: string, initialValue: T) {
4
- const [storedValue, setStoredValue] = useState<T>(() => {
5
- try {
6
- const item = window.localStorage.getItem(key);
7
- return item ? (JSON.parse(item) as T) : initialValue;
8
- } catch {
9
- return initialValue;
10
- }
11
- });
12
-
13
- const setValue = useCallback(
14
- (value: T | ((prev: T) => T)) => {
15
- try {
16
- const valueToStore =
17
- value instanceof Function ? value(storedValue) : value;
18
- setStoredValue(valueToStore);
19
- window.localStorage.setItem(key, JSON.stringify(valueToStore));
20
- } catch (error) {
21
- console.error(error);
22
- }
23
- },
24
- [key, storedValue]
25
- );
26
-
27
- return [storedValue, setValue] as const;
28
- }
@@ -1,20 +0,0 @@
1
- /**
2
- * @typedef {Object} User
3
- * @property {string} id
4
- * @property {string} email
5
- * @property {string} name
6
- */
7
-
8
- /**
9
- * @typedef {Object} ApiResponse
10
- * @property {*} data
11
- * @property {string} message
12
- * @property {boolean} success
13
- */
14
-
15
- /**
16
- * @typedef {Object} CreateUserPayload
17
- * @property {string} email
18
- * @property {string} name
19
- */
20
- export {};
@@ -1,16 +0,0 @@
1
- export interface User {
2
- id: string;
3
- email: string;
4
- name: string;
5
- }
6
-
7
- export interface ApiResponse<T> {
8
- data: T;
9
- message: string;
10
- success: boolean;
11
- }
12
-
13
- export interface CreateUserPayload {
14
- email: string;
15
- name: string;
16
- }
@@ -1,3 +0,0 @@
1
- export function cn(...inputs) {
2
- return inputs.filter(Boolean).join(" ");
3
- }
@@ -1,5 +0,0 @@
1
- type ClassValue = string | false | null | undefined;
2
-
3
- export function cn(...inputs: ClassValue[]): string {
4
- return inputs.filter(Boolean).join(" ");
5
- }
@@ -1,11 +0,0 @@
1
- import { describe, it, expect } from "@jest/globals";
2
- import { render, screen } from "@testing-library/react";
3
- import "@testing-library/jest-dom/jest-globals";
4
- import App from "@/App";
5
-
6
- describe("App", () => {
7
- it("renders without crashing", () => {
8
- render(<App />);
9
- expect(screen.getByText(/Welcome/i)).toBeInTheDocument();
10
- });
11
- });
@@ -1,11 +0,0 @@
1
- import { describe, it, expect } from "@jest/globals";
2
- import { render, screen } from "@testing-library/react";
3
- import "@testing-library/jest-dom/jest-globals";
4
- import App from "@/App";
5
-
6
- describe("App", () => {
7
- it("renders without crashing", () => {
8
- render(<App />);
9
- expect(screen.getByText(/Welcome/i)).toBeInTheDocument();
10
- });
11
- });
@@ -1,10 +0,0 @@
1
- import { render, screen } from "@testing-library/react";
2
- import { describe, it, expect } from "vitest";
3
- import App from "@/App";
4
-
5
- describe("App", () => {
6
- it("renders without crashing", () => {
7
- render(<App />);
8
- expect(screen.getByText(/Welcome/i)).toBeInTheDocument();
9
- });
10
- });
@@ -1,10 +0,0 @@
1
- import { render, screen } from "@testing-library/react";
2
- import { describe, it, expect } from "vitest";
3
- import App from "@/App";
4
-
5
- describe("App", () => {
6
- it("renders without crashing", () => {
7
- render(<App />);
8
- expect(screen.getByText(/Welcome/i)).toBeInTheDocument();
9
- });
10
- });