@undp/create-app 0.0.1

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 (41) hide show
  1. package/bin/generateFiles.js +65 -0
  2. package/bin/generateTemplates/copyTemplate.js +18 -0
  3. package/bin/generateTemplates/generateIndexHtml.js +15 -0
  4. package/bin/generateTemplates/generatePackageJson.js +105 -0
  5. package/bin/generateTemplates/generateReadme.js +143 -0
  6. package/bin/generateTemplates/generateStyleCss.js +6 -0
  7. package/bin/generateTemplates/index.js +6 -0
  8. package/bin/generateTemplates/templates/basic/App.txt +32 -0
  9. package/bin/generateTemplates/templates/basic/AppWithContainer.txt +32 -0
  10. package/bin/generateTemplates/templates/basic/main.txt +10 -0
  11. package/bin/generateTemplates/templates/configFiles/.prettierrc +10 -0
  12. package/bin/generateTemplates/templates/configFiles/eslint.config.js +97 -0
  13. package/bin/generateTemplates/templates/configFiles/icon.txt +1 -0
  14. package/bin/generateTemplates/templates/configFiles/staticwebapp.config.json +44 -0
  15. package/bin/generateTemplates/templates/configFiles/tailwind.config.js +7 -0
  16. package/bin/generateTemplates/templates/configFiles/tsconfig.json +29 -0
  17. package/bin/generateTemplates/templates/configFiles/tsconfig.node.json +9 -0
  18. package/bin/generateTemplates/templates/configFiles/vite-env.txt +1 -0
  19. package/bin/generateTemplates/templates/configFiles/vite.config.ts.txt +109 -0
  20. package/bin/generateTemplates/templates/configFiles/viteWithoutPostCss.config.ts.txt +58 -0
  21. package/bin/generateTemplates/templates/css/fonts.css +213 -0
  22. package/bin/generateTemplates/templates/query/App.txt +57 -0
  23. package/bin/generateTemplates/templates/query/AppWithContainer.txt +67 -0
  24. package/bin/generateTemplates/templates/query/main.txt +29 -0
  25. package/bin/generateTemplates/templates/router/App.txt +30 -0
  26. package/bin/generateTemplates/templates/router/components/Footer.txt +20 -0
  27. package/bin/generateTemplates/templates/router/components/Header.txt +29 -0
  28. package/bin/generateTemplates/templates/router/main.txt +60 -0
  29. package/bin/generateTemplates/templates/router/routes/About.txt +28 -0
  30. package/bin/generateTemplates/templates/router+query/App.txt +30 -0
  31. package/bin/generateTemplates/templates/router+query/components/Footer.txt +20 -0
  32. package/bin/generateTemplates/templates/router+query/components/Header.txt +29 -0
  33. package/bin/generateTemplates/templates/router+query/integration/tanstack-query.txt +27 -0
  34. package/bin/generateTemplates/templates/router+query/main.txt +69 -0
  35. package/bin/generateTemplates/templates/router+query/routes/queryDemo.txt +56 -0
  36. package/bin/index.js +57 -0
  37. package/bin/promptUser.js +96 -0
  38. package/bin/utils/createFolders.js +11 -0
  39. package/bin/utils/index.js +2 -0
  40. package/bin/utils/printSuccess.js +23 -0
  41. package/package.json +32 -0
@@ -0,0 +1,28 @@
1
+ import { createRoute } from '@tanstack/react-router';
2
+ import type { RootRoute } from '@tanstack/react-router';
3
+
4
+ function About() {
5
+ return (
6
+ <div className='m-5'>
7
+ <div>
8
+ <img
9
+ src={undpLogo}
10
+ className='logo react mb-8'
11
+ alt='React logo'
12
+ width='72px'
13
+ style={{ marginLeft: 'auto', marginRight: 'auto' }}
14
+ />
15
+ </div>
16
+ <H3 className='text-center'>
17
+ This is an about page
18
+ </H3>
19
+ </div>
20
+ );
21
+ }
22
+
23
+ export default (parentRoute: RootRoute) =>
24
+ createRoute({
25
+ path: '/about',
26
+ component: About,
27
+ getParentRoute: () => parentRoute,
28
+ });
@@ -0,0 +1,30 @@
1
+ import { H3 } from '@undp/design-system-react';
2
+
3
+ import '@/styles/fonts.css';
4
+ import '@/styles/style.css';
5
+ import undpLogo from './assets/undp-logo-blue.svg';
6
+
7
+ function App() {
8
+ return (
9
+ <div className='m-5'>
10
+ <div>
11
+ <img
12
+ src={undpLogo}
13
+ className='logo react mb-8'
14
+ alt='React logo'
15
+ width='72px'
16
+ style={{ marginLeft: 'auto', marginRight: 'auto' }}
17
+ />
18
+ </div>
19
+ <H3 className='text-center'>
20
+ This is template for building visualization and frontend project
21
+ maintained by UNDP&apos;s DAI Hub.
22
+ <br />
23
+ <br />
24
+ Contact us at data@undp.org if you have any feedback or questions.
25
+ </H3>
26
+ </div>
27
+ );
28
+ }
29
+
30
+ export default App;
@@ -0,0 +1,20 @@
1
+ import {
2
+ Footer,
3
+ FooterLogoUnit,
4
+ FooterCopyrightUnit,
5
+ } from '@undp/design-system-react/Footer';
6
+
7
+
8
+
9
+ export default function Header() {
10
+ return (
11
+ <Footer>
12
+ <FooterLogoUnit>
13
+ subscribe to email
14
+ </FooterLogoUnit>
15
+ <FooterCopyrightUnit>
16
+ Footnote can be added here
17
+ </FooterCopyrightUnit>
18
+ </Footer>
19
+ )
20
+ }
@@ -0,0 +1,29 @@
1
+ import {
2
+ Header,
3
+ HeaderLogoUnit,
4
+ HeaderMainNavUnit,
5
+ HeaderMenuUnit,
6
+ } from '@undp/design-system-react/Header';
7
+ import { Link } from '@tanstack/react-router';
8
+
9
+ export default function Header() {
10
+ return (
11
+ <Header>
12
+ <HeaderLogoUnit
13
+ hyperlink="./"
14
+ siteName="Site name"
15
+ siteSubName="Sub-site name"
16
+ />
17
+ <HeaderMainNavUnit>
18
+ <HeaderMenuUnit>
19
+ <Link to='/'>
20
+ Home
21
+ </Link>
22
+ <Link to='/query-demo'>
23
+ Query Demo
24
+ </Link>
25
+ </HeaderMenuUnit>
26
+ </HeaderMainNavUnit>
27
+ </Header>
28
+ )
29
+ }
@@ -0,0 +1,27 @@
1
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
2
+
3
+ export function getContext() {
4
+ const queryClient = new QueryClient({
5
+ defaultOptions: {
6
+ queries: {
7
+ staleTime: 1000 * 60 * 60 * 24, // how long fetched data is considered ā€œfreshā€ before it becomes ā€œstaleā€ 🔢 24 hrs
8
+ gcTime: 1000 * 60 * 60 * 24, // how long inactive (unused) query data stays in memory before being deleted 🔢 24 hrs
9
+ },
10
+ },
11
+ });
12
+ return {
13
+ queryClient,
14
+ }
15
+ }
16
+
17
+ export function Provider({
18
+ children,
19
+ queryClient,
20
+ }: {
21
+ children: React.ReactNode
22
+ queryClient: QueryClient
23
+ }) {
24
+ return (
25
+ <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
26
+ )
27
+ }
@@ -0,0 +1,69 @@
1
+ import { StrictMode } from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import {
4
+ Outlet,
5
+ RouterProvider,
6
+ createRootRoute,
7
+ createRoute,
8
+ createRouter,
9
+ } from '@tanstack/react-router';
10
+
11
+ import Header from './components/Header';
12
+ import Footer from './components/Footer';
13
+ import * as TanStackQueryProvider from './integration/tanstack-query';
14
+
15
+ import './styles/fonts.css';
16
+ import './styles/style.css';
17
+
18
+ import App from './App';
19
+
20
+ const rootRoute = createRootRoute({
21
+ component: () => (
22
+ <>
23
+ <Header />
24
+ <Outlet />
25
+ <Footer />
26
+ </>
27
+ ),
28
+ });
29
+
30
+ const indexRoute = createRoute({
31
+ getParentRoute: () => rootRoute,
32
+ path: '/',
33
+ component: App,
34
+ });
35
+
36
+ const routeTree = rootRoute.addChildren([
37
+ indexRoute,
38
+ TanStackQueryDemo(rootRoute),
39
+ ]);
40
+
41
+ const TanStackQueryProviderContext = TanStackQueryProvider.getContext();
42
+ const router = createRouter({
43
+ routeTree,
44
+ context: {
45
+ ...TanStackQueryProviderContext,
46
+ },
47
+ defaultPreload: 'intent',
48
+ scrollRestoration: true,
49
+ defaultStructuralSharing: true,
50
+ defaultPreloadStaleTime: 0,
51
+ });
52
+
53
+ declare module '@tanstack/react-router' {
54
+ interface Register {
55
+ router: typeof router;
56
+ }
57
+ }
58
+
59
+ const rootElement = document.getElementById('app');
60
+ if (rootElement && !rootElement.innerHTML) {
61
+ const root = ReactDOM.createRoot(rootElement);
62
+ root.render(
63
+ <StrictMode>
64
+ <TanStackQueryProvider.Provider {...TanStackQueryProviderContext}>
65
+ <RouterProvider router={router} />
66
+ </TanStackQueryProvider.Provider>
67
+ </StrictMode>,
68
+ );
69
+ }
@@ -0,0 +1,56 @@
1
+ import { createRoute } from '@tanstack/react-router';
2
+ import { useQuery } from '@tanstack/react-query';
3
+ import type { RootRoute } from '@tanstack/react-router';
4
+ import { Spinner } from '@undp/design-system-react/Spinner';
5
+
6
+ function useTodoData() {
7
+ return useQuery({
8
+ queryKey: ['todos'],
9
+ queryFn: () =>
10
+ Promise.resolve([
11
+ { id: 1, name: 'Alice' },
12
+ { id: 2, name: 'Bob' },
13
+ { id: 3, name: 'Charlie' },
14
+ ]),
15
+ initialData: [],
16
+ });
17
+ }
18
+
19
+ function TanStackQueryDemo() {
20
+ const { data, isLoading, isError } = useTodoData();
21
+
22
+ if (isLoading) return <Spinner size='lg' className='my-20 m-auto' />;
23
+
24
+ if (isError)
25
+ return (
26
+ <div className='px-4 mx-auto'>
27
+ <div>Error</div>
28
+ </div>
29
+ );
30
+ return (
31
+ <div className='m-5'>
32
+ <div>
33
+ <img
34
+ src={undpLogo}
35
+ className='logo react mb-8'
36
+ alt='React logo'
37
+ width='72px'
38
+ style={{ marginLeft: 'auto', marginRight: 'auto' }}
39
+ />
40
+ </div>
41
+ <H3 className='text-center'>
42
+ Data loaded successfully
43
+ <br />
44
+ <br />
45
+ {data.length} elements in the query
46
+ </H3>
47
+ </div>
48
+ );
49
+ }
50
+
51
+ export default (parentRoute: RootRoute) =>
52
+ createRoute({
53
+ path: '/query-demo',
54
+ component: TanStackQueryDemo,
55
+ getParentRoute: () => parentRoute,
56
+ });
package/bin/index.js ADDED
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+
3
+ import path from 'path';
4
+ import inquirer from 'inquirer';
5
+ import chalk from 'chalk';
6
+ import { promptUser } from './promptUser.js';
7
+ import { generateFiles } from './generateFiles.js';
8
+ import { printSuccess, createFolders } from './utils/index.js';
9
+
10
+ async function main() {
11
+ // Get user configuration
12
+ const args = process.argv.slice(2);
13
+ const projectName = args[0];
14
+ const config = await promptUser(projectName);
15
+
16
+ const projectPath = path.resolve(process.cwd(), config.projectName);
17
+ console.log(chalk.gray('\n' + '─'.repeat(60)));
18
+ console.log(chalk.bold.green(`\nšŸ“ Creating project at: ${chalk.cyan(projectPath)}\n`));
19
+
20
+ createFolders(projectPath, true);
21
+
22
+ process.chdir(projectPath);
23
+
24
+
25
+ createFolders(path.join(projectPath, 'public'), false);
26
+ createFolders(path.join(projectPath, 'src'), false);
27
+ createFolders(path.join(projectPath, 'src', 'styles'), false);
28
+ createFolders(path.join(projectPath, 'src', 'assets'), false);
29
+
30
+ if(config.installRouter) {
31
+ createFolders(path.join(projectPath, 'src', 'routes'), false);
32
+ createFolders(path.join(projectPath, 'src', 'components'), false);
33
+ }
34
+
35
+ if(config.installRouter && config.installQuery) {
36
+ createFolders(path.join(projectPath, 'src', 'integration'), false);
37
+ }
38
+
39
+ generateFiles(config);
40
+
41
+ const { installNow } = await inquirer.prompt([
42
+ {
43
+ type: 'confirm',
44
+ name: 'installNow',
45
+ message: 'Would you like to install dependencies now?',
46
+ default: true,
47
+ },
48
+ ]);
49
+
50
+ console.log(chalk.bold.yellow('\nšŸ“¦ Installing dependencies (this might take some time)...'));
51
+ if (installNow) execSync('npm install', { stdio: 'inherit' })
52
+ console.log(chalk.green(' āœ“ All dependencies installed'));
53
+
54
+ printSuccess(config, installNow);
55
+ }
56
+
57
+ main().catch(console.error);
@@ -0,0 +1,96 @@
1
+ import readline from 'readline';
2
+ import inquirer from 'inquirer';
3
+ import chalk from 'chalk';
4
+
5
+ const rl = readline.createInterface({
6
+ input: process.stdin,
7
+ output: process.stdout,
8
+ });
9
+
10
+ const question = (query) =>
11
+ new Promise(resolve => rl.question(query, resolve));
12
+
13
+ export async function promptUser(name) {
14
+ console.log(
15
+ chalk.bold.cyan("\nšŸš€ UNDP Frontend Starter Kit | Let's configure your application") +
16
+ chalk.gray('\nšŸ”§ Powered by React, Vite, TypeScript, ESLint, Prettier, Tailwind, and React Compiler\n')
17
+ );
18
+ console.log(chalk.gray('─'.repeat(60)) + '\n');
19
+
20
+ const projectName = name ? name : (await question(chalk.yellow('šŸ“ Enter project name: '))) || 'my-undp-react-app';
21
+
22
+ const { libraries } = await inquirer.prompt([
23
+ {
24
+ type: 'checkbox',
25
+ name: 'libraries',
26
+ message: chalk.yellow('šŸ“¦ Select the libraries you want to install:'),
27
+ choices: [
28
+ { name: '@undp/data-viz (for visualizations)', value: '@undp/data-viz' },
29
+ { name: 'lucide-react (for icons)', value: 'lucide-react' },
30
+ { name: '@tanstack/react-router (for routing)', value: '@tanstack/react-router' },
31
+ { name: '@tanstack/react-query (for state management and fetching data from api)', value: '@tanstack/react-query' },
32
+ ],
33
+ default: ['@undp/data-viz', 'lucide-react'], // optional default selection
34
+ },
35
+ ]);
36
+ const installLucide = libraries.includes('lucide-react');
37
+ const installDataViz = libraries.includes('@undp/data-viz');
38
+ const installRouter = libraries.includes('@tanstack/react-router');
39
+ const installQuery = libraries.includes('@tanstack/react-query');
40
+
41
+ let installDataVizPeerDeps = false;
42
+ if (installDataViz) {
43
+ const { peerDeps } = await inquirer.prompt([
44
+ {
45
+ type: 'list',
46
+ name: 'peerDeps',
47
+ message: chalk.yellow('šŸ“¦ Add peer dependencies for @undp/data-viz?'),
48
+ choices: ['Yes', 'No'],
49
+ default: 'Yes',
50
+ },
51
+ ]);
52
+ installDataVizPeerDeps = peerDeps === 'Yes'
53
+ }
54
+
55
+ let addPostCSSScripts = false;
56
+
57
+ if(!installRouter) {
58
+ const { postCSS } = await inquirer.prompt([
59
+ {
60
+ type: 'list',
61
+ name: 'postCSS',
62
+ message: chalk.yellow(
63
+ 'āš™ļø Add PostCSS script to flatten layers, wrap all classes in `.undp-container`, and reorder media queries (recommended if embedding in another app)?'
64
+ ),
65
+ choices: ['Yes', 'No'],
66
+ default: 'Yes',
67
+ },
68
+ ]);
69
+ addPostCSSScripts = postCSS === 'Yes';
70
+ }
71
+
72
+ const { staticWebApp } = await inquirer.prompt([
73
+ {
74
+ type: 'list',
75
+ name: 'staticWebApp',
76
+ message: chalk.yellow('āš™ļø Add Azure Static Web App Config file?'),
77
+ choices: ['Yes', 'No'],
78
+ default: 'No',
79
+ },
80
+ ]);
81
+
82
+ // Convert string responses to booleans for convenience
83
+ const addStaticWebAppConfig = staticWebApp === 'Yes';
84
+
85
+ rl.close();
86
+ return {
87
+ projectName,
88
+ installLucide,
89
+ installDataViz,
90
+ installDataVizPeerDeps,
91
+ addStaticWebAppConfig,
92
+ addPostCSSScripts,
93
+ installRouter,
94
+ installQuery
95
+ };
96
+ }
@@ -0,0 +1,11 @@
1
+ import fs from 'fs';
2
+ import chalk from 'chalk';
3
+
4
+ export function createFolders(dir, showError){
5
+ if (!fs.existsSync(dir)) {
6
+ fs.mkdirSync(dir, { recursive: true });
7
+ } else if(showError) {
8
+ console.error(chalk.red(`āŒ Error: Folder "${config.projectName}" already exists.`));
9
+ throw new Error(`Project folder already exists at ${projectPath}`);
10
+ }
11
+ }
@@ -0,0 +1,2 @@
1
+ export { printSuccess } from './printSuccess.js';
2
+ export { createFolders } from './createFolders.js';
@@ -0,0 +1,23 @@
1
+
2
+ import chalk from 'chalk';
3
+
4
+ export function printSuccess(config, installNow){
5
+ console.log(chalk.bold.green('\nāœ… Project created successfully!\n'));
6
+
7
+ console.log(chalk.cyan('šŸ“¦ Added packages:'));
8
+ console.log(chalk.gray(' • React + TypeScript + Vite'));
9
+ console.log(chalk.gray(' • Tailwind CSS'));
10
+ console.log(chalk.gray(' • ESLint + Prettier'));
11
+ console.log(chalk.gray(' • @undp/design-system-react'));
12
+ if (config.installLucide) console.log(chalk.gray(' • lucide-react'));
13
+ if (config.installDataViz) console.log(chalk.gray(' • @undp/data-viz'));
14
+ if (config.installQuery) console.log(chalk.gray(' • @tanstack/react-query'));
15
+ if (config.installRouter) console.log(chalk.gray(' • @tanstack/react-router'));
16
+
17
+ console.log(chalk.cyan('\nšŸš€ Next steps:'));
18
+ console.log(chalk.white(` cd ${chalk.bold(config.projectName)}`));
19
+ if(!installNow) console.log(chalk.white(` ${chalk.bold('npm install')}`));
20
+ console.log(chalk.white(` ${chalk.bold('npm run dev')}`));
21
+
22
+ console.log(chalk.dim('\nHappy coding! šŸŽ‰\n'));
23
+ }
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@undp/create-app",
3
+ "version": "0.0.1",
4
+ "description": "UNDP's project scaffolding tool",
5
+ "bin": {
6
+ "create-undp-app": "./bin/index.js"
7
+ },
8
+ "scripts": {
9
+ "dev": "node ./bin/index.js"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "files": ["bin", "dist", "templates"],
15
+ "keywords": [
16
+ "UNDP",
17
+ "scaffold",
18
+ "generator",
19
+ "cli"
20
+ ],
21
+ "dependencies": {
22
+ "chalk": "^4.1.2",
23
+ "fs-extra": "^11.1.0",
24
+ "inquirer": "^8.2.7"
25
+ },
26
+ "author": "UNDP",
27
+ "license": "ISC",
28
+ "type": "module",
29
+ "devDependencies": {
30
+ "@types/node": "^24.10.0"
31
+ }
32
+ }