@undp/create-app 0.2.2 β†’ 0.2.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # πŸ—οΈ UNDP Create App CLI ![npm](https://img.shields.io/npm/v/@undp/create-app)
2
2
 
3
- **`@undp/create-app`** is UNDP’s official **project scaffolding tool** for quickly bootstrapping frontend applications using **React**, and **TypeScript** β€” complete with sensible defaults for ESLint, Prettier, and UNDP’s Design System.
3
+ **`@undp/create-app`** is UNDP’s official **project scaffolding tool** for quickly bootstrapping frontend applications using **React**, and **TypeScript** β€” complete with sensible defaults for ESLint, Prettier.
4
4
 
5
5
  [NPM Package](https://www.npmjs.com/package/@undp/create-app)
6
6
 
@@ -8,14 +8,15 @@
8
8
 
9
9
  ## πŸš€ Features
10
10
 
11
- - ⚑ **React + TypeScript** setup out of the box
12
- - 🎨 Includes **Tailwind CSS** for styling
13
- - 🧠 Includes **Zustand** for state management
14
- - πŸš€ **Vite support** for fast SPA development, including optional **routing** and **data fetching** with **TanStack** libraries
11
+ - βš›οΈ **React + TypeScript** setup out of the box
12
+ - πŸ–ŒοΈ Includes **Tailwind CSS** for styling
13
+ - πŸ—‚οΈ Includes **Zustand** for state management
14
+ - πŸš€ **Vite support** for fast SPA development, including optional **routing** with **Tanstack router**
15
15
  - 🌐 **Next.js support** for full-stack apps, including optional **authentication** with **Better Auth**
16
- - 🧱 Preconfigured for **UNDP’s Design System**
16
+ - 🎨 Preconfigured for **UNDP’s Design System**
17
+ - ⚑️ Optional integration with **Tanstack query** for data fetching
17
18
  - πŸ“Š Optional integration with **@undp/data-viz** for interactive data visualization
18
- - 🧼 **ESLint** + **Prettier** preconfigured for consistent code style
19
+ - ✨ **ESLint** + **Prettier** preconfigured for consistent code style
19
20
 
20
21
  ---
21
22
 
@@ -26,9 +27,7 @@ All setups include **Tailwind CSS** for styling and **Zustand** for state manage
26
27
  | Framework | Variants | Description |
27
28
  |------------|-----------|-------------|
28
29
  | **Vite** | Basic | Minimal React + TypeScript setup |
29
- | **Vite** | + Query | Adds TanStack Query for data fetching |
30
30
  | **Vite** | + Router | Adds TanStack Router for routing |
31
- | **Vite** | + Router + Query | Full-featured Vite setup |
32
31
  | **Next.js** | Basic | SSR and file-based routing |
33
32
  | **Next.js** | + Auth | Includes authentication via Better Auth |
34
33
 
@@ -0,0 +1,73 @@
1
+ export function generateGitIgnore(isVite) {
2
+ if (isVite) return `# Logs
3
+ logs
4
+ *.log
5
+ npm-debug.log*
6
+ yarn-debug.log*
7
+ yarn-error.log*
8
+ pnpm-debug.log*
9
+ lerna-debug.log*
10
+
11
+ node_modules
12
+ build
13
+ dist
14
+ dist-ssr
15
+ *.local
16
+ .eslintcache
17
+ stats.html
18
+
19
+ # Editor directories and files
20
+ .vscode/*
21
+ !.vscode/extensions.json
22
+ .idea
23
+ .DS_Store
24
+ *.suo
25
+ *.ntvs*
26
+ *.njsproj
27
+ *.sln
28
+ *.sw?
29
+ `
30
+ return `# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
31
+
32
+ # dependencies
33
+ /node_modules
34
+ /.pnp
35
+ .pnp.*
36
+ .yarn/*
37
+ !.yarn/patches
38
+ !.yarn/plugins
39
+ !.yarn/releases
40
+ !.yarn/versions
41
+
42
+ # testing
43
+ /coverage
44
+
45
+ # next.js
46
+ /.next/
47
+ /out/
48
+
49
+ # production
50
+ /build
51
+
52
+ # misc
53
+ .DS_Store
54
+ *.pem
55
+
56
+ # debug
57
+ npm-debug.log*
58
+ yarn-debug.log*
59
+ yarn-error.log*
60
+ .pnpm-debug.log*
61
+
62
+ # env files (can opt-in for committing if needed)
63
+ .env*
64
+
65
+ # vercel
66
+ .vercel
67
+
68
+ # typescript
69
+ *.tsbuildinfo
70
+ next-env.d.ts
71
+ `
72
+ }
73
+
@@ -1,8 +1,10 @@
1
- export function generateLayoutForNext(dataViz, projectName) {
1
+ export function generateLayoutForNext(dataViz, projectName, query) {
2
2
  return `import type { Metadata } from 'next';
3
+ import { ReactNode } from 'react';
3
4
 
4
5
  import HeaderEl from '@/components/Header';
5
- import FooterEl from '@/components/Footer';
6
+ import FooterEl from '@/components/Footer';${query ? `
7
+ import TanStackQueryProvider from '@/integration/tanstack-query';`:''}
6
8
 
7
9
  import './globals.css';
8
10
  import '@undp/design-system-react/style.css';${dataViz ? `
@@ -16,14 +18,17 @@ export const metadata: Metadata = {
16
18
  export default function RootLayout({
17
19
  children,
18
20
  }: Readonly<{
19
- children: React.ReactNode;
21
+ children: ReactNode;
20
22
  }>) {
21
23
  return (
22
24
  <html lang='en'>
23
25
  <body className='flex flex-col gap-0 min-h-screen'>
24
26
  <HeaderEl />
25
- <main className='grow-1 flex flex-col justify-center'>
26
- <div className='flex flex-col justify-center'>{children}</div>
27
+ <main className='grow-1 flex flex-col justify-center'>${query ? `
28
+ <TanStackQueryProvider>
29
+ <div className='flex flex-col justify-center'>{children}</div>
30
+ </TanStackQueryProvider>` : `
31
+ <div className='flex flex-col justify-center'>{children}</div>`}
27
32
  </main>
28
33
  <FooterEl />
29
34
  </body>
@@ -38,16 +38,9 @@ export function generatePackageJson(config) {
38
38
  };
39
39
 
40
40
  switch (config.framework) {
41
- case 'vite-query':
42
- dependencies['@tanstack/react-query'] = '^5.90.7';
43
- break;
44
41
  case 'vite-router':
45
42
  dependencies['@tanstack/react-router'] = '^1.135.0';
46
43
  break;
47
- case 'vite-full':
48
- dependencies['@tanstack/react-query'] = '^5.90.7';
49
- dependencies['@tanstack/react-router'] = '^1.135.0';
50
- break;
51
44
  case 'next-basic':
52
45
  dependencies['next'] = '16.0.1';
53
46
  break;
@@ -58,6 +51,9 @@ export function generatePackageJson(config) {
58
51
  default:
59
52
  break;
60
53
  }
54
+ if (config.query) {
55
+ dependencies['@tanstack/react-query'] = '^5.90.7';
56
+ }
61
57
  if (config.libraries.includes('@undp/data-viz')) {
62
58
  const dataVizVer = `^${getLatestVersion('@undp/data-viz')}`
63
59
  dependencies['@undp/data-viz'] = dataVizVer;
@@ -0,0 +1,102 @@
1
+ export function generatePageForNext(query) {
2
+ return `'use client';
3
+
4
+ import { P } from '@undp/design-system-react/Typography';
5
+ import { Button } from '@undp/design-system-react/Button';
6
+ import Image from 'next/image';${query ? `
7
+ import { useQuery } from '@tanstack/react-query';
8
+ import { Spinner } from '@undp/design-system-react/Spinner';` :''}
9
+
10
+ import { useCounterActions, useCounter } from '@/stores/counter';${query ? `
11
+
12
+ function useTodoData() {
13
+ return useQuery({
14
+ queryKey: ['todos'],
15
+ queryFn: () =>
16
+ Promise.resolve([
17
+ { id: 1, name: 'Alice' },
18
+ { id: 2, name: 'Bob' },
19
+ { id: 3, name: 'Charlie' },
20
+ ]),
21
+ });
22
+ }` :''}
23
+
24
+ export default function Home() {
25
+ const count = useCounter();
26
+ const { increment, decrement } = useCounterActions();${query ? `
27
+
28
+ const { data, isLoading, isError } = useTodoData();
29
+
30
+ if (isLoading) return <Spinner size='lg' className='my-20 m-auto' />;
31
+
32
+ if (isError) return <>Error</>;` : ''}
33
+ return (
34
+ <>
35
+ <div className='flex gap-4 items-center justify-center my-8 mx-auto'>
36
+ <Image
37
+ className='dark:invert'
38
+ src='/imgs/next.svg'
39
+ alt='Next.js logo'
40
+ width={100}
41
+ height={20}
42
+ priority
43
+ />
44
+ <P marginBottom='none'>&</P>
45
+ <Image
46
+ className='dark:invert'
47
+ src='/imgs/Tailwind_CSS_Logo.svg'
48
+ alt='Tailwind logo'
49
+ width={40}
50
+ height={20}
51
+ priority
52
+ />
53
+ <P marginBottom='none'>&</P>
54
+ <Image
55
+ className='dark:invert'
56
+ src='/imgs/Zustand-logo.svg'
57
+ alt='Zustand logo'
58
+ width={40}
59
+ height={20}
60
+ priority
61
+ />
62
+ <P marginBottom='none'>&</P>
63
+ <Image
64
+ className='dark:invert'
65
+ src='/imgs/undp-logo-blue.svg'
66
+ alt='UNDP logo'
67
+ width={40}
68
+ height={20}
69
+ priority
70
+ />
71
+ </div>
72
+ <P marginBottom='xl' className='text-center'>
73
+ To get started, edit the App.tsx file.{' '}
74
+ <span className='font-bold'>Count: {count}</span>
75
+ </P>
76
+ <div className='flex gap-4 justify-center mb-8'>
77
+ <Button
78
+ variant='tertiary'
79
+ onClick={() => {
80
+ increment();
81
+ }}
82
+ >
83
+ Increase counter
84
+ </Button>
85
+ <Button
86
+ variant='tertiary'
87
+ onClick={() => {
88
+ decrement();
89
+ }}
90
+ >
91
+ Decrease counter
92
+ </Button>
93
+ </div>${query ? `
94
+ <P marginBottom='xl' className='text-center'>
95
+ Data loaded successfully. {data?.length} elements in the query.
96
+ </P>` : ''}
97
+ </>
98
+ );
99
+ }
100
+ `
101
+ }
102
+
@@ -6,3 +6,5 @@ export { generateLayoutForNext } from './generateLayoutForNext.js';
6
6
  export { generateStylesCss } from './generateStylesCss.js';
7
7
  export { generateNextEnvTypes } from './generateNextEnvTypes.js';
8
8
  export { generateEnv } from './generateEnv.js';
9
+ export { generatePageForNext } from './generatePageForNext.js';
10
+ export { generateGitIgnore } from './generateGitIgnore.js';
package/bin/index.js CHANGED
@@ -17,6 +17,8 @@ import {
17
17
  generateLayoutForNext,
18
18
  generateNextEnvTypes,
19
19
  generateEnv,
20
+ generatePageForNext,
21
+ generateGitIgnore,
20
22
  } from './generateFiles/index.js';
21
23
 
22
24
  function copyFolder(source, destination) {
@@ -45,14 +47,17 @@ async function main() {
45
47
  let tertiaryFolder = 'basic';
46
48
 
47
49
  switch (config.framework) {
48
- case 'vite-query':
49
- tertiaryFolder = 'query';
50
+ case 'vite-basic':
51
+ if (config.query)
52
+ tertiaryFolder = 'query';
53
+ else
54
+ tertiaryFolder = 'basic';
50
55
  break;
51
56
  case 'vite-router':
52
- tertiaryFolder = 'router';
53
- break;
54
- case 'vite-full':
55
- tertiaryFolder = 'query+router';
57
+ if (config.query)
58
+ tertiaryFolder = 'query+router';
59
+ else
60
+ tertiaryFolder = 'router';
56
61
  break;
57
62
  case 'next-auth':
58
63
  tertiaryFolder = 'auth';
@@ -73,10 +78,13 @@ async function main() {
73
78
  }
74
79
  } else {
75
80
  copyFolder(path.join(__dirname, `./templates/${baseFolder}/${tertiaryFolder}`), projectPath);
76
- fs.writeFileSync('app/layout.tsx', generateLayoutForNext(config.libraries.includes('@undp/data-viz'), config.projectName));
81
+ fs.writeFileSync('app/layout.tsx', generateLayoutForNext(config.libraries.includes('@undp/data-viz'), config.projectName, config.query));
77
82
  fs.writeFileSync('next-env.d.ts', generateNextEnvTypes());
83
+ fs.writeFileSync('app/page.tsx', generatePageForNext(config.query));
78
84
  if (config.framework === 'next-auth') fs.writeFileSync('.env.local', generateEnv());
85
+ if (config.query) copyFolder(path.join(__dirname, './templates/queryIntegrationForNext'), projectPath);
79
86
  }
87
+ fs.writeFileSync('.gitignore', generateGitIgnore(config.framework.includes('vite')));
80
88
  fs.writeFileSync('package.json', JSON.stringify(generatePackageJson(config), null, 2));
81
89
 
82
90
  console.log(chalk.green(' βœ“ Project folder and files generated\n'));
package/bin/promptUser.js CHANGED
@@ -25,17 +25,29 @@ export async function promptUser(name) {
25
25
  loop: false,
26
26
  message: chalk.yellow('πŸ“¦ Choose your setup (all include Tailwind for styling & Zustand for state management):\n'),
27
27
  choices: [
28
- { name: 'Vite β€” Basic React setup', value: 'vite-basic' },
29
- { name: 'Vite + Query β€” With TanStack Query', value: 'vite-query' },
28
+ { name: 'Vite β€” Basic React setup (ideal for embedding)', value: 'vite-basic' },
30
29
  { name: 'Vite + Router β€” With routing support', value: 'vite-router' },
31
- { name: 'Vite + Router + Query β€” Full Vite setup', value: 'vite-full' },
32
30
  { name: 'Next.js β€” SSR and file routing', value: 'next-basic' },
33
31
  { name: 'Next.js + Auth β€” With authentication', value: 'next-auth' },
34
32
  ],
35
33
  default: 'vite-basic',
36
34
  },
35
+ ]);
36
+
37
+ const { addQuery } = await inquirer.prompt([
38
+ {
39
+ type: 'list',
40
+ name: 'addQuery',
41
+ message: chalk.yellow(
42
+ 'βš™οΈ Add @tanstack/query for data fetching?'
43
+ ),
44
+ choices: ['Yes', 'No'],
45
+ default: 'No',
46
+ },
37
47
  ]);
38
48
 
49
+ const query = addQuery === 'Yes';
50
+
39
51
  const libraryChoices = [
40
52
  {
41
53
  name: '@undp/data-viz β€” UNDP data visualization components',
@@ -97,6 +109,7 @@ export async function promptUser(name) {
97
109
  libraries,
98
110
  addStaticWebAppConfig,
99
111
  addPostCSSScripts,
100
- framework,
112
+ framework,
113
+ query,
101
114
  };
102
115
  }
@@ -1,4 +1,5 @@
1
1
  import type { Metadata } from 'next';
2
+ import { ReactNode } from 'react';
2
3
 
3
4
  import HeaderEl from '@/components/Header';
4
5
  import FooterEl from '@/components/Footer';
@@ -14,7 +15,7 @@ export const metadata: Metadata = {
14
15
  export default function RootLayout({
15
16
  children,
16
17
  }: Readonly<{
17
- children: React.ReactNode;
18
+ children: ReactNode;
18
19
  }>) {
19
20
  return (
20
21
  <html lang='en'>
@@ -1,4 +1,5 @@
1
1
  import type { Metadata } from 'next';
2
+ import { ReactNode } from 'react';
2
3
 
3
4
  import HeaderEl from '@/components/Header';
4
5
  import FooterEl from '@/components/Footer';
@@ -15,7 +16,7 @@ export const metadata: Metadata = {
15
16
  export default function RootLayout({
16
17
  children,
17
18
  }: Readonly<{
18
- children: React.ReactNode;
19
+ children: ReactNode;
19
20
  }>) {
20
21
  return (
21
22
  <html lang='en'>
@@ -0,0 +1,27 @@
1
+ 'use client';
2
+
3
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
4
+ import { ReactNode, useState } from 'react';
5
+
6
+ export default function TanStackQueryProvider({
7
+ children,
8
+ }: {
9
+ children: ReactNode;
10
+ }) {
11
+ // Create client only once
12
+ const [queryClient] = useState(
13
+ () =>
14
+ new QueryClient({
15
+ defaultOptions: {
16
+ queries: {
17
+ staleTime: 1000 * 60 * 60 * 24, // how long fetched data is considered β€œfresh” before it becomes β€œstale” 👒 24 hrs
18
+ gcTime: 1000 * 60 * 60 * 24, // how long inactive (unused) query data stays in memory before being deleted 👒 24 hrs
19
+ },
20
+ },
21
+ }),
22
+ );
23
+
24
+ return (
25
+ <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
26
+ );
27
+ }
@@ -1,4 +1,5 @@
1
1
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
2
+ import { ReactNode } from 'react';
2
3
 
3
4
  export function getContext() {
4
5
  const queryClient = new QueryClient({
@@ -18,7 +19,7 @@ export function Provider({
18
19
  children,
19
20
  queryClient,
20
21
  }: {
21
- children: React.ReactNode;
22
+ children: ReactNode;
22
23
  queryClient: QueryClient;
23
24
  }) {
24
25
  return (
@@ -1,4 +1,5 @@
1
1
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
2
+ import { ReactNode } from 'react';
2
3
 
3
4
  export function getContext() {
4
5
  const queryClient = new QueryClient({
@@ -18,7 +19,7 @@ export function Provider({
18
19
  children,
19
20
  queryClient,
20
21
  }: {
21
- children: React.ReactNode;
22
+ children: ReactNode;
22
23
  queryClient: QueryClient;
23
24
  }) {
24
25
  return (
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@undp/create-app",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "UNDP's project scaffolding tool",
5
5
  "bin": {
6
6
  "create-undp-app": "./bin/index.js"