@oss-ma/tpl 1.0.29 → 1.0.30

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.
@@ -1,7 +1,38 @@
1
1
  // cli/src/engine/render.ts
2
+ /**
3
+ * Render a template string with variable substitution and conditional blocks.
4
+ *
5
+ * Supported syntax:
6
+ * {{variableName}} — replaced by vars[variableName]
7
+ * {{#if variableName}}...{{/if}} — block included only if vars[variableName] is truthy and !== "none"
8
+ * {{#unless variableName}}...{{/unless}} — block included only if vars[variableName] is falsy or === "none"
9
+ *
10
+ * Blocks can span multiple lines.
11
+ * Nested blocks are not supported.
12
+ */
2
13
  export function renderString(input, vars) {
3
- return input.replace(/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g, (_, key) => {
14
+ // 1) Process {{#if var}}...{{/if}} blocks
15
+ let output = input.replace(/\{\{#if\s+([a-zA-Z0-9_]+)\s*\}\}([\s\S]*?)\{\{\/if\}\}/g, (_, key, content) => {
16
+ const val = vars[key];
17
+ return isActive(val) ? content : "";
18
+ });
19
+ // 2) Process {{#unless var}}...{{/unless}} blocks
20
+ output = output.replace(/\{\{#unless\s+([a-zA-Z0-9_]+)\s*\}\}([\s\S]*?)\{\{\/unless\}\}/g, (_, key, content) => {
21
+ const val = vars[key];
22
+ return isActive(val) ? "" : content;
23
+ });
24
+ // 3) Replace {{variable}} placeholders
25
+ output = output.replace(/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g, (_, key) => {
4
26
  const v = vars[key];
5
- return v !== undefined ? String(v) : `{{${key}}}`; // keep unresolved visible
27
+ return v !== undefined ? String(v) : `{{${key}}}`;
6
28
  });
29
+ // 4) Clean up blank lines left by removed blocks (max 1 consecutive blank line)
30
+ output = output.replace(/\n{3,}/g, "\n\n");
31
+ return output;
32
+ }
33
+ /**
34
+ * A variable is "active" if it exists, is not empty, and is not "none".
35
+ */
36
+ function isActive(val) {
37
+ return val !== undefined && val !== "" && val !== "none";
7
38
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oss-ma/tpl",
3
- "version": "1.0.29",
3
+ "version": "1.0.30",
4
4
  "description": "Generate, enforce and maintain clean project architectures",
5
5
  "type": "module",
6
6
  "repository": {
@@ -18,17 +18,17 @@
18
18
  "audit": "npm audit --audit-level=high"
19
19
  },
20
20
  "dependencies": {
21
- "@tanstack/react-query": "^5.56.0",
22
21
  "react": "^18.3.0",
23
- "react-dom": "^18.3.0",
24
- "react-router-dom": "^6.26.0",
25
- "zustand": "^4.5.0"
22
+ "react-dom": "^18.3.0"{{#if routing}},
23
+ "react-router-dom": "^6.26.0"{{/if}}{{#if state}},
24
+ "zustand": "^4.5.0"{{/if}}{{#if fetching}},
25
+ "@tanstack/react-query": "^5.56.0"{{/if}}
26
26
  },
27
27
  "devDependencies": {
28
28
  "@commitlint/cli": "^19.3.0",
29
29
  "@commitlint/config-conventional": "^19.2.0",
30
- "@eslint/js": "^9.0.0",
31
- "@tanstack/react-query-devtools": "^5.56.0",
30
+ "@eslint/js": "^9.0.0",{{#if fetching}}
31
+ "@tanstack/react-query-devtools": "^5.56.0",{{/if}}
32
32
  "@testing-library/jest-dom": "^6.5.0",
33
33
  "@testing-library/react": "^16.0.0",
34
34
  "@testing-library/user-event": "^14.5.0",
@@ -1,3 +1,4 @@
1
+ {{#if routing}}
1
2
  import { Routes, Route } from "react-router-dom";
2
3
  import { HomePage } from "@/pages/HomePage";
3
4
  import { NotFoundPage } from "@/pages/NotFoundPage";
@@ -9,4 +10,12 @@ export function App() {
9
10
  <Route path="*" element={<NotFoundPage />} />
10
11
  </Routes>
11
12
  );
12
- }
13
+ }
14
+ {{/if}}
15
+ {{#unless routing}}
16
+ import { HomePage } from "@/pages/HomePage";
17
+
18
+ export function App() {
19
+ return <HomePage />;
20
+ }
21
+ {{/unless}}
@@ -1,27 +1,41 @@
1
1
  import { StrictMode } from "react";
2
2
  import { createRoot } from "react-dom/client";
3
+ {{#if routing}}
3
4
  import { BrowserRouter } from "react-router-dom";
5
+ {{/if}}
6
+ {{#if fetching}}
4
7
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
5
8
  import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
9
+ {{/if}}
6
10
  import { App } from "@/app/App";
7
11
  import "@/app/index.css";
12
+ {{#if fetching}}
8
13
 
9
14
  const queryClient = new QueryClient({
10
15
  defaultOptions: {
11
16
  queries: {
12
- staleTime: 1000 * 60 * 5, // 5 minutes
17
+ staleTime: 1000 * 60 * 5,
13
18
  retry: 1,
14
19
  },
15
20
  },
16
21
  });
22
+ {{/if}}
17
23
 
18
24
  createRoot(document.getElementById("root")!).render(
19
25
  <StrictMode>
26
+ {{#if fetching}}
20
27
  <QueryClientProvider client={queryClient}>
28
+ {{/if}}
29
+ {{#if routing}}
21
30
  <BrowserRouter>
31
+ {{/if}}
22
32
  <App />
33
+ {{#if routing}}
23
34
  </BrowserRouter>
35
+ {{/if}}
36
+ {{#if fetching}}
24
37
  <ReactQueryDevtools initialIsOpen={false} />
25
38
  </QueryClientProvider>
39
+ {{/if}}
26
40
  </StrictMode>
27
41
  );
@@ -1,6 +1,11 @@
1
+ {{#if fetching}}
1
2
  import { useQuery } from "@tanstack/react-query";
3
+ {{/if}}
4
+ {{#if state}}
2
5
  import { useCounterStore } from "./store";
6
+ {{/if}}
3
7
  import { Button } from "@/shared/ui/Button";
8
+ {{#if fetching}}
4
9
 
5
10
  interface Post {
6
11
  id: number;
@@ -12,18 +17,23 @@ async function fetchPosts(): Promise<Post[]> {
12
17
  if (!res.ok) throw new Error("Failed to fetch posts");
13
18
  return res.json();
14
19
  }
20
+ {{/if}}
15
21
 
16
22
  export function ExampleFeature() {
23
+ {{#if state}}
17
24
  const { count, increment, decrement, reset } = useCounterStore();
25
+ {{/if}}
26
+ {{#if fetching}}
18
27
 
19
28
  const { data: posts, isLoading, isError } = useQuery({
20
29
  queryKey: ["posts"],
21
30
  queryFn: fetchPosts,
22
31
  });
32
+ {{/if}}
23
33
 
24
34
  return (
25
35
  <section style={{ marginTop: "2rem" }}>
26
- {/* Zustand counter */}
36
+ {{#if state}}
27
37
  <div style={{ marginBottom: "2rem" }}>
28
38
  <h2>Counter (Zustand)</h2>
29
39
  <p style={{ fontSize: "2rem", margin: "0.5rem 0" }}>{count}</p>
@@ -33,8 +43,8 @@ export function ExampleFeature() {
33
43
  <Button onClick={reset} variant="secondary">Reset</Button>
34
44
  </div>
35
45
  </div>
36
-
37
- {/* React Query fetch */}
46
+ {{/if}}
47
+ {{#if fetching}}
38
48
  <div>
39
49
  <h2>Posts (TanStack Query)</h2>
40
50
  {isLoading && <p>Loading...</p>}
@@ -49,6 +59,12 @@ export function ExampleFeature() {
49
59
  </ul>
50
60
  )}
51
61
  </div>
62
+ {{/if}}
63
+ {{#unless state}}
64
+ {{#unless fetching}}
65
+ <p>Feature module — add your components here.</p>
66
+ {{/unless}}
67
+ {{/unless}}
52
68
  </section>
53
69
  );
54
70
  }
@@ -1,13 +1,37 @@
1
+ {{#if state}}
1
2
  import { ExampleFeature } from "@/features/example/ExampleFeature";
3
+ {{/if}}
4
+ {{#unless state}}
5
+ {{#unless fetching}}
6
+ import { Button } from "@/shared/ui/Button";
7
+ import { useState } from "react";
8
+ {{/unless}}
9
+ {{/unless}}
2
10
 
3
11
  export function HomePage() {
12
+ {{#unless state}}
13
+ {{#unless fetching}}
14
+ const [msg, setMsg] = useState<string | null>(null);
15
+ {{/unless}}
16
+ {{/unless}}
17
+
4
18
  return (
5
19
  <main style={{ padding: "2rem", maxWidth: 800, margin: "0 auto" }}>
6
20
  <h1>{{appName}}</h1>
7
21
  <p style={{ marginTop: "0.5rem", opacity: 0.7 }}>
8
22
  React + TypeScript + Vite
9
23
  </p>
24
+ {{#if state}}
10
25
  <ExampleFeature />
26
+ {{/if}}
27
+ {{#unless state}}
28
+ {{#unless fetching}}
29
+ <section style={{ marginTop: "2rem" }}>
30
+ <Button onClick={() => setMsg("Hello!")}>Click me</Button>
31
+ {msg && <p style={{ marginTop: "0.5rem" }}>{msg}</p>}
32
+ </section>
33
+ {{/unless}}
34
+ {{/unless}}
11
35
  </main>
12
36
  );
13
37
  }
@@ -1,6 +1,6 @@
1
1
  name: react-ts
2
- version: 1.0.0
3
- description: "Template React + TypeScript (Vite) with lint/format/test/build + CI + ADR"
2
+ version: 2.0.0
3
+ description: "Template React + TypeScript (Vite) with optional routing, state and fetching"
4
4
  engine: "v1"
5
5
 
6
6
  questions:
@@ -13,6 +13,21 @@ questions:
13
13
  choices: ["npm", "pnpm", "yarn"]
14
14
  default: "npm"
15
15
 
16
+ - name: routing
17
+ message: "Routing ?"
18
+ choices: ["react-router", "none"]
19
+ default: "react-router"
20
+
21
+ - name: state
22
+ message: "State management ?"
23
+ choices: ["zustand", "none"]
24
+ default: "zustand"
25
+
26
+ - name: fetching
27
+ message: "Data fetching ?"
28
+ choices: ["tanstack-query", "none"]
29
+ default: "tanstack-query"
30
+
16
31
  hooks:
17
32
  postGenerate:
18
33
  - run: "git init"