create-ec-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.
@@ -0,0 +1,885 @@
1
+ import chalk from "chalk";
2
+ import ora from "ora";
3
+ import fs from "fs-extra";
4
+ import path from "path";
5
+ import { execSync } from "child_process";
6
+ import inquirer from "inquirer";
7
+ // Helper function to run commands and show a spinner
8
+ const runCommand = (command, spinnerMessage) => {
9
+ const spinner = ora(spinnerMessage).start();
10
+ try {
11
+ execSync(command, { stdio: "pipe" });
12
+ spinner.succeed();
13
+ }
14
+ catch (error) {
15
+ spinner.fail();
16
+ console.error(chalk.red(`Failed to execute command: ${command}`));
17
+ console.error(error);
18
+ process.exit(1);
19
+ }
20
+ };
21
+ // Template functions
22
+ const getKendoTailwindPreset = () => `module.exports = {
23
+ theme: {
24
+ extend: {
25
+ spacing: {
26
+ 1: "var( --kendo-spacing-1 )",
27
+ 1.5: "var( --kendo-spacing-1.5 )",
28
+ 2: "var( --kendo-spacing-2 )",
29
+ 2.5: "var( --kendo-spacing-2.5 )",
30
+ 3: "var( --kendo-spacing-3 )",
31
+ 3.5: "var( --kendo-spacing-3.5 )",
32
+ 4: "var( --kendo-spacing-4 )",
33
+ 4.5: "var( --kendo-spacing-4.5 )",
34
+ 5: "var( --kendo-spacing-5 )",
35
+ 5.5: "var( --kendo-spacing-5.5 )",
36
+ 6: "var( --kendo-spacing-6 )",
37
+ 6.5: "var( --kendo-spacing-6.5 )",
38
+ 7: "var( --kendo-spacing-7 )",
39
+ 7.5: "var( --kendo-spacing-7.5 )",
40
+ 8: "var( --kendo-spacing-8 )",
41
+ 9: "var( --kendo-spacing-9 )",
42
+ 10: "var( --kendo-spacing-10 )",
43
+ 11: "var( --kendo-spacing-11 )",
44
+ 12: "var( --kendo-spacing-12 )",
45
+ 13: "var( --kendo-spacing-13 )",
46
+ 14: "var( --kendo-spacing-14 )",
47
+ 15: "var( --kendo-spacing-15 )",
48
+ 16: "var( --kendo-spacing-16 )",
49
+ 17: "var( --kendo-spacing-17 )",
50
+ 18: "var( --kendo-spacing-18 )",
51
+ 19: "var( --kendo-spacing-19 )",
52
+ 20: "var( --kendo-spacing-20 )",
53
+ 21: "var( --kendo-spacing-21 )",
54
+ 22: "var( --kendo-spacing-22 )",
55
+ 23: "var( --kendo-spacing-23 )",
56
+ 24: "var( --kendo-spacing-24 )",
57
+ 25: "var( --kendo-spacing-25 )",
58
+ 26: "var( --kendo-spacing-26 )",
59
+ 27: "var( --kendo-spacing-27 )",
60
+ 28: "var( --kendo-spacing-28 )",
61
+ 29: "var( --kendo-spacing-29 )",
62
+ 30: "var( --kendo-spacing-30 )",
63
+ },
64
+ borderRadius: {
65
+ none: "var( --kendo-border-radius-none )",
66
+ sm: "var( --kendo-border-radius-sm )",
67
+ DEFAULT: "var( --kendo-border-radius-md )",
68
+ lg: "var( --kendo-border-radius-lg )",
69
+ xl: "var( --kendo-border-radius-xl )",
70
+ "2xl": "var( --kendo-border-radius-xxl )",
71
+ "3xl": "var( --kendo-border-radius-xxxl )",
72
+ full: "var( --kendo-border-radius-none )",
73
+ },
74
+ boxShadow: {
75
+ sm: "var( --kendo-elevation-2 )",
76
+ DEFAULT: "var( --kendo-elevation-4 )",
77
+ lg: "var( --kendo-elevation-6 )",
78
+ xl: "var( --kendo-elevation-8 )",
79
+ "2xl": "var( --kendo-elevation-9 )",
80
+ },
81
+ colors: {
82
+ "app-surface": "var( --kendo-color-app-surface )",
83
+ "on-app-surface": "var( --kendo-color-on-app-surface )",
84
+ subtle: "var( --kendo-color-subtle )",
85
+ surface: "var( --kendo-color-surface )",
86
+ "surface-alt": "var( --kendo-color-surface-alt )",
87
+ border: "var( --kendo-color-border )",
88
+ "border-alt": "var( --kendo-color-border-alt )",
89
+ base: {
90
+ DEFAULT: "var( --kendo-color-base )",
91
+ hover: "var( --kendo-color-base-hover )",
92
+ active: "var( --kendo-color-base-active )",
93
+ emphasis: "var( --kendo-color-base-emphasis )",
94
+ subtle: "var( --kendo-color-base-subtle )",
95
+ "subtle-hover": "var( --kendo-color-base-subtle-hover )",
96
+ "subtle-active": "var( --kendo-color-base-subtle-active)",
97
+ "on-subtle": "var( --kendo-color-base-on-subtle )",
98
+ "on-surface": "var( --kendo-color-base-on-surface )",
99
+ },
100
+ "on-base": "var( --kendo-color-on-base )",
101
+ primary: {
102
+ DEFAULT: "var( --kendo-color-primary )",
103
+ hover: "var( --kendo-color-primary-hover )",
104
+ active: "var( --kendo-color-primary-active )",
105
+ emphasis: "var( --kendo-color-primary-emphasis )",
106
+ subtle: "var( --kendo-color-primary-subtle )",
107
+ "subtle-hover": "var( --kendo-color-primary-subtle-hover )",
108
+ "subtle-active": "var( --kendo-color-primary-subtle-active)",
109
+ "on-subtle": "var( --kendo-color-primary-on-subtle )",
110
+ "on-surface": "var( --kendo-color-primary-on-surface )",
111
+ },
112
+ "on-primary": "var( --kendo-color-on-primary )",
113
+ secondary: {
114
+ DEFAULT: "var( --kendo-color-secondary )",
115
+ hover: "var( --kendo-color-secondary-hover )",
116
+ active: "var( --kendo-color-secondary-active )",
117
+ emphasis: "var( --kendo-color-secondary-emphasis )",
118
+ subtle: "var( --kendo-color-secondary-subtle )",
119
+ "subtle-hover": "var( --kendo-color-secondary-subtle-hover )",
120
+ "subtle-active": "var( --kendo-color-secondary-subtle-active)",
121
+ "on-subtle": "var( --kendo-color-secondary-on-subtle )",
122
+ "on-surface": "var( --kendo-color-secondary-on-surface )",
123
+ },
124
+ "on-secondary": "var( --kendo-color-on-secondary )",
125
+ tertiary: {
126
+ DEFAULT: "var( --kendo-color-tertiary )",
127
+ hover: "var( --kendo-color-tertiary-hover )",
128
+ active: "var( --kendo-color-tertiary-active )",
129
+ emphasis: "var( --kendo-color-tertiary-emphasis )",
130
+ subtle: "var( --kendo-color-tertiary-subtle )",
131
+ "subtle-hover": "var( --kendo-color-tertiary-subtle-hover )",
132
+ "subtle-active": "var( --kendo-color-tertiary-subtle-active)",
133
+ "on-subtle": "var( --kendo-color-tertiary-on-subtle )",
134
+ "on-surface": "var( --kendo-color-tertiary-on-surface )",
135
+ },
136
+ "on-tertiary": "var( --kendo-color-on-tertiary )",
137
+ info: {
138
+ DEFAULT: "var( --kendo-color-info )",
139
+ hover: "var( --kendo-color-info-hover )",
140
+ active: "var( --kendo-color-info-active )",
141
+ emphasis: "var( --kendo-color-info-emphasis )",
142
+ subtle: "var( --kendo-color-info-subtle )",
143
+ "subtle-hover": "var( --kendo-color-info-subtle-hover )",
144
+ "subtle-active": "var( --kendo-color-info-subtle-active)",
145
+ "on-subtle": "var( --kendo-color-info-on-subtle )",
146
+ "on-surface": "var( --kendo-color-info-on-surface )",
147
+ },
148
+ "on-info": "var( --kendo-color-on-info )",
149
+ success: {
150
+ DEFAULT: "var( --kendo-color-success )",
151
+ hover: "var( --kendo-color-success-hover )",
152
+ active: "var( --kendo-color-success-active )",
153
+ emphasis: "var( --kendo-color-success-emphasis )",
154
+ subtle: "var( --kendo-color-success-subtle )",
155
+ "subtle-hover": "var( --kendo-color-success-subtle-hover )",
156
+ "subtle-active": "var( --kendo-color-success-subtle-active)",
157
+ "on-subtle": "var( --kendo-color-success-on-subtle )",
158
+ "on-surface": "var( --kendo-color-success-on-surface )",
159
+ },
160
+ "on-success": "var( --kendo-color-on-success )",
161
+ error: {
162
+ DEFAULT: "var( --kendo-color-error )",
163
+ hover: "var( --kendo-color-error-hover )",
164
+ active: "var( --kendo-color-error-active )",
165
+ emphasis: "var( --kendo-color-error-emphasis )",
166
+ subtle: "var( --kendo-color-error-subtle )",
167
+ "subtle-hover": "var( --kendo-color-error-subtle-hover )",
168
+ "subtle-active": "var( --kendo-color-error-subtle-active)",
169
+ "on-subtle": "var( --kendo-color-error-on-subtle )",
170
+ "on-surface": "var( --kendo-color-error-on-surface )",
171
+ },
172
+ "on-error": "var( --kendo-color-on-error )",
173
+ warning: {
174
+ DEFAULT: "var( --kendo-color-warning )",
175
+ hover: "var( --kendo-color-warning-hover )",
176
+ active: "var( --kendo-color-warning-active )",
177
+ emphasis: "var( --kendo-color-warning-emphasis )",
178
+ subtle: "var( --kendo-color-warning-subtle )",
179
+ "subtle-hover": "var( --kendo-color-warning-subtle-hover )",
180
+ "subtle-active": "var( --kendo-color-warning-subtle-active)",
181
+ "on-subtle": "var( --kendo-color-warning-on-subtle )",
182
+ "on-surface": "var( --kendo-color-warning-on-surface )",
183
+ },
184
+ "on-warning": "var( --kendo-color-on-warning )",
185
+ },
186
+ },
187
+ },
188
+ };`;
189
+ const getTailwindConfig = () => `/** @type {import('tailwindcss').Config} */
190
+ const kendoTwPreset = require('./kendo-tw-preset.js');
191
+
192
+ module.exports = {
193
+ content: [
194
+ './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
195
+ './src/components/**/*.{js,ts,jsx,tsx,mdx}',
196
+ './src/app/**/*.{js,ts,jsx,tsx,mdx}',
197
+ ],
198
+ presets: [kendoTwPreset],
199
+ theme: {
200
+ extend: {},
201
+ },
202
+ plugins: [],
203
+ }`;
204
+ const getLayoutTemplate = (kendoThemePackage) => `import { Inter } from 'next/font/google'
205
+ import '@progress/kendo-theme-${kendoThemePackage.split("-").pop()}/dist/all.css'
206
+ import './globals.css'
207
+ import { Providers } from './providers'
208
+
209
+ const inter = Inter({ subsets: ['latin'] })
210
+
211
+ export const metadata = {
212
+ title: 'Portal App',
213
+ description: 'Next.js Portal application with Kendo UI',
214
+ }
215
+
216
+ export default function RootLayout({
217
+ children,
218
+ }: {
219
+ children: React.ReactNode
220
+ }) {
221
+ return (
222
+ <html lang="en">
223
+ <body className={inter.className}>
224
+ <Providers>
225
+ {children}
226
+ </Providers>
227
+ </body>
228
+ </html>
229
+ )
230
+ }`;
231
+ const getShadcnLayoutTemplate = () => `import { Inter } from 'next/font/google'
232
+ import './globals.css'
233
+ import { Providers } from './providers'
234
+
235
+ const inter = Inter({ subsets: ['latin'] })
236
+
237
+ export const metadata = {
238
+ title: 'Portal App',
239
+ description: 'Next.js Portal application with Shadcn/ui',
240
+ }
241
+
242
+ export default function RootLayout({
243
+ children,
244
+ }: {
245
+ children: React.ReactNode
246
+ }) {
247
+ return (
248
+ <html lang="en">
249
+ <body className={inter.className}>
250
+ <Providers>
251
+ {children}
252
+ </Providers>
253
+ </body>
254
+ </html>
255
+ )
256
+ }`;
257
+ const getProvidersTemplate = () => `'use client'
258
+
259
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
260
+ import { SessionProvider } from 'next-auth/react'
261
+ import { useState } from 'react'
262
+
263
+ export function Providers({ children }: { children: React.ReactNode }) {
264
+ const [queryClient] = useState(() => new QueryClient({
265
+ defaultOptions: {
266
+ queries: {
267
+ refetchOnWindowFocus: false,
268
+ retry: 3,
269
+ staleTime: 5 * 60 * 1000, // 5 minutes
270
+ },
271
+ mutations: {
272
+ retry: 1,
273
+ },
274
+ },
275
+ }))
276
+
277
+ return (
278
+ <SessionProvider>
279
+ <QueryClientProvider client={queryClient}>
280
+ {children}
281
+ </QueryClientProvider>
282
+ </SessionProvider>
283
+ )
284
+ }`;
285
+ const getShadcnProvidersTemplate = () => `'use client'
286
+
287
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
288
+ import { SessionProvider } from 'next-auth/react'
289
+ import { useState } from 'react'
290
+
291
+ export function Providers({ children }: { children: React.ReactNode }) {
292
+ const [queryClient] = useState(() => new QueryClient({
293
+ defaultOptions: {
294
+ queries: {
295
+ refetchOnWindowFocus: false,
296
+ retry: 3,
297
+ staleTime: 5 * 60 * 1000, // 5 minutes
298
+ },
299
+ mutations: {
300
+ retry: 1,
301
+ },
302
+ },
303
+ }))
304
+
305
+ return (
306
+ <SessionProvider>
307
+ <QueryClientProvider client={queryClient}>
308
+ {children}
309
+ </QueryClientProvider>
310
+ </SessionProvider>
311
+ )
312
+ }`;
313
+ const getPageTemplate = () => `'use client';
314
+
315
+ import { Button } from '@progress/kendo-react-buttons';
316
+ import { signIn, signOut, useSession } from 'next-auth/react';
317
+ import { useDynamicsAccounts } from '@/hooks/useDynamicsAccounts';
318
+
319
+ export default function Home() {
320
+ const { data: session, status } = useSession();
321
+ const { data: accounts, isLoading, error } = useDynamicsAccounts();
322
+
323
+ if (status === 'loading') {
324
+ return (
325
+ <main className="flex min-h-screen flex-col items-center justify-center p-24">
326
+ <div className="text-center">
327
+ <h1 className="text-4xl font-bold mb-8">Loading...</h1>
328
+ </div>
329
+ </main>
330
+ );
331
+ }
332
+
333
+ if (!session) {
334
+ return (
335
+ <main className="flex min-h-screen flex-col items-center justify-center p-24">
336
+ <div className="text-center">
337
+ <h1 className="text-4xl font-bold mb-8">Welcome to Portal App</h1>
338
+ <p className="text-lg mb-8">Sign in to access your Dynamics 365 data</p>
339
+ <Button onClick={() => signIn('azure-ad')}>
340
+ Sign in with Microsoft
341
+ </Button>
342
+ </div>
343
+ </main>
344
+ );
345
+ }
346
+
347
+ return (
348
+ <main className="flex min-h-screen flex-col p-24">
349
+ <div className="mb-8">
350
+ <h1 className="text-4xl font-bold mb-4">Welcome, {session.user?.name}!</h1>
351
+ <Button onClick={() => signOut()}>Sign out</Button>
352
+ </div>
353
+
354
+ <div className="mb-8">
355
+ <h2 className="text-2xl font-bold mb-4">Dynamics 365 Accounts</h2>
356
+
357
+ {isLoading && <p>Loading accounts...</p>}
358
+ {error && <p className="text-red-500">Error loading accounts: {error.message}</p>}
359
+
360
+ {accounts && (
361
+ <div className="grid gap-4">
362
+ {accounts.map((account) => (
363
+ <div key={account.accountid} className="border p-4 rounded">
364
+ <h3 className="font-semibold">{account.name}</h3>
365
+ <p className="text-gray-600">{account.emailaddress1}</p>
366
+ <p className="text-sm text-gray-500">ID: {account.accountid}</p>
367
+ </div>
368
+ ))}
369
+ </div>
370
+ )}
371
+ </div>
372
+ </main>
373
+ );
374
+ }`;
375
+ const getShadcnPageTemplate = () => `'use client';
376
+
377
+ import { Button } from '@/components/ui/button';
378
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
379
+ import { signIn, signOut, useSession } from 'next-auth/react';
380
+ import { useDynamicsAccounts } from '@/hooks/useDynamicsAccounts';
381
+
382
+ export default function Home() {
383
+ const { data: session, status } = useSession();
384
+ const { data: accounts, isLoading, error } = useDynamicsAccounts();
385
+
386
+ if (status === 'loading') {
387
+ return (
388
+ <main className="flex min-h-screen flex-col items-center justify-center p-24">
389
+ <div className="text-center">
390
+ <h1 className="text-4xl font-bold mb-8">Loading...</h1>
391
+ </div>
392
+ </main>
393
+ );
394
+ }
395
+
396
+ if (!session) {
397
+ return (
398
+ <main className="flex min-h-screen flex-col items-center justify-center p-24">
399
+ <div className="text-center">
400
+ <h1 className="text-4xl font-bold mb-8">Welcome to Portal App</h1>
401
+ <p className="text-lg mb-8">Sign in to access your Dynamics 365 data</p>
402
+ <Button onClick={() => signIn('azure-ad')}>
403
+ Sign in with Microsoft
404
+ </Button>
405
+ </div>
406
+ </main>
407
+ );
408
+ }
409
+
410
+ return (
411
+ <main className="flex min-h-screen flex-col p-24">
412
+ <div className="mb-8">
413
+ <h1 className="text-4xl font-bold mb-4">Welcome, {session.user?.name}!</h1>
414
+ <Button onClick={() => signOut()}>Sign out</Button>
415
+ </div>
416
+
417
+ <div className="mb-8">
418
+ <h2 className="text-2xl font-bold mb-4">Dynamics 365 Accounts</h2>
419
+
420
+ {isLoading && <p>Loading accounts...</p>}
421
+ {error && <p className="text-red-500">Error loading accounts: {error.message}</p>}
422
+
423
+ {accounts && (
424
+ <div className="grid gap-4">
425
+ {accounts.map((account) => (
426
+ <Card key={account.accountid}>
427
+ <CardHeader>
428
+ <CardTitle>{account.name}</CardTitle>
429
+ <CardDescription>{account.emailaddress1}</CardDescription>
430
+ </CardHeader>
431
+ <CardContent>
432
+ <p className="text-sm text-gray-500">ID: {account.accountid}</p>
433
+ </CardContent>
434
+ </Card>
435
+ ))}
436
+ </div>
437
+ )}
438
+ </div>
439
+ </main>
440
+ );
441
+ }`;
442
+ const getGlobalsCSS = () => `@tailwind base;
443
+ @tailwind components;
444
+ @tailwind utilities;
445
+
446
+ :root {
447
+ --foreground-rgb: 0, 0, 0;
448
+ --background-start-rgb: 214, 219, 220;
449
+ --background-end-rgb: 255, 255, 255;
450
+ }
451
+
452
+ @media (prefers-color-scheme: dark) {
453
+ :root {
454
+ --foreground-rgb: 255, 255, 255;
455
+ --background-start-rgb: 0, 0, 0;
456
+ --background-end-rgb: 0, 0, 0;
457
+ }
458
+ }
459
+
460
+ body {
461
+ color: rgb(var(--foreground-rgb));
462
+ background: linear-gradient(
463
+ to bottom,
464
+ transparent,
465
+ rgb(var(--background-end-rgb))
466
+ )
467
+ rgb(var(--background-start-rgb));
468
+ }`;
469
+ const getPrettierConfig = () => `{
470
+ "tabWidth": 2,
471
+ "useTabs": false,
472
+ "semi": true,
473
+ "singleQuote": true,
474
+ "trailingComma": "es5",
475
+ "bracketSpacing": true,
476
+ "jsxBracketSameLine": false,
477
+ "arrowParens": "always",
478
+ "printWidth": 80
479
+ }`;
480
+ const getEnvConfig = () => `# NextAuth.js Configuration
481
+ NEXTAUTH_URL=http://localhost:3000
482
+
483
+ # Microsoft Azure AD Configuration
484
+ AZURE_AD_CLIENT_ID=your-azure-ad-client-id
485
+ AZURE_AD_CLIENT_SECRET=your-azure-ad-client-secret
486
+ AZURE_AD_TENANT_ID=your-azure-ad-tenant-id
487
+
488
+ # Dynamics 365 Configuration
489
+ DYNAMICS_BASE_URL=https://your-org.crm.dynamics.com
490
+ DYNAMICS_API_VERSION=v9.2
491
+ `;
492
+ const authConfig = () => `import NextAuth from 'next-auth'
493
+ import AzureADProvider from 'next-auth/providers/azure-ad'
494
+
495
+ export const { handlers, signIn, signOut, auth } = NextAuth({
496
+ providers: [
497
+ AzureADProvider({
498
+ clientId: process.env.AZURE_AD_CLIENT_ID!,
499
+ clientSecret: process.env.AZURE_AD_CLIENT_SECRET!,
500
+ tenantId: process.env.AZURE_AD_TENANT_ID!,
501
+ }),
502
+ ],
503
+ callbacks: {
504
+ async jwt({ token, account }) {
505
+ if (account) {
506
+ token.accessToken = account.access_token
507
+ }
508
+ return token
509
+ },
510
+ async session({ session, token }) {
511
+ session.accessToken = token.accessToken as string
512
+ return session
513
+ },
514
+ },
515
+ pages: {
516
+ signIn: '/auth/signin',
517
+ error: '/auth/error',
518
+ },
519
+ })`;
520
+ const getAuthRoute = () => `import { handlers } from '@/auth'
521
+
522
+ export const { GET, POST } = handlers`;
523
+ const getDynamicsAccountsRoute = () => `import { auth } from '@/auth'
524
+ import { getDynamicsData } from '@/lib/dynamics'
525
+ import { NextResponse } from 'next/server'
526
+
527
+ export async function GET() {
528
+ try {
529
+ const session = await auth()
530
+
531
+ if (!session) {
532
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
533
+ }
534
+
535
+ const accounts = await getDynamicsData('accounts', {
536
+ select: ['accountid', 'name', 'emailaddress1', 'telephone1', 'websiteurl'],
537
+ top: 10,
538
+ })
539
+
540
+ return NextResponse.json(accounts)
541
+ } catch (error) {
542
+ console.error('Error fetching accounts:', error)
543
+ return NextResponse.json(
544
+ { error: 'Failed to fetch accounts' },
545
+ { status: 500 }
546
+ )
547
+ }
548
+ }`;
549
+ const getDynamicsLib = () => `export interface DynamicsAccount {
550
+ accountid: string
551
+ name: string
552
+ emailaddress1?: string
553
+ telephone1?: string
554
+ websiteurl?: string
555
+ }
556
+
557
+ interface QueryOptions {
558
+ select?: string[]
559
+ filter?: string
560
+ orderby?: string
561
+ top?: number
562
+ skip?: number
563
+ }
564
+
565
+ export async function getDynamicsData(
566
+ entityName: string,
567
+ options: QueryOptions = {}
568
+ ): Promise<any> {
569
+ const baseUrl = process.env.DYNAMICS_BASE_URL
570
+ const apiVersion = process.env.DYNAMICS_API_VERSION || 'v9.2'
571
+
572
+ if (!baseUrl) {
573
+ throw new Error('DYNAMICS_BASE_URL environment variable is not set')
574
+ }
575
+
576
+ let url = \`\${baseUrl}/api/data/\${apiVersion}/\${entityName}\`
577
+ const params = new URLSearchParams()
578
+
579
+ if (options.select) {
580
+ params.append('$select', options.select.join(','))
581
+ }
582
+
583
+ if (options.filter) {
584
+ params.append('$filter', options.filter)
585
+ }
586
+
587
+ if (options.orderby) {
588
+ params.append('$orderby', options.orderby)
589
+ }
590
+
591
+ if (options.top) {
592
+ params.append('$top', options.top.toString())
593
+ }
594
+
595
+ if (options.skip) {
596
+ params.append('$skip', options.skip.toString())
597
+ }
598
+
599
+ const queryString = params.toString()
600
+ if (queryString) {
601
+ url += \`?\${queryString}\`
602
+ }
603
+
604
+ const response = await fetch(url, {
605
+ headers: {
606
+ 'Accept': 'application/json',
607
+ 'OData-MaxVersion': '4.0',
608
+ 'OData-Version': '4.0',
609
+ 'Prefer': 'odata.include-annotations="*"',
610
+ },
611
+ })
612
+
613
+ if (!response.ok) {
614
+ throw new Error(\`HTTP error! status: \${response.status}\`)
615
+ }
616
+
617
+ const data = await response.json()
618
+ return data.value || data
619
+ }`;
620
+ const getDynamicsAccountsHook = () => `'use client'
621
+
622
+ import { useQuery } from '@tanstack/react-query'
623
+ import { DynamicsAccount } from '@/lib/dynamics'
624
+
625
+ export function useDynamicsAccounts() {
626
+ return useQuery<DynamicsAccount[]>({
627
+ queryKey: ['dynamics', 'accounts'],
628
+ queryFn: async () => {
629
+ const response = await fetch('/api/dynamics/accounts')
630
+
631
+ if (!response.ok) {
632
+ throw new Error('Failed to fetch accounts')
633
+ }
634
+
635
+ return response.json()
636
+ },
637
+ staleTime: 5 * 60 * 1000, // 5 minutes
638
+ retry: 3,
639
+ })
640
+ }`;
641
+ const compilerOptions = () => `{
642
+ "compilerOptions": {
643
+ "lib": ["dom", "dom.iterable", "es6"],
644
+ "allowJs": true,
645
+ "skipLibCheck": true,
646
+ "strict": true,
647
+ "noEmit": true,
648
+ "esModuleInterop": true,
649
+ "module": "esnext",
650
+ "moduleResolution": "bundler",
651
+ "resolveJsonModule": true,
652
+ "isolatedModules": true,
653
+ "jsx": "preserve",
654
+ "incremental": true,
655
+ "plugins": [
656
+ {
657
+ "name": "next"
658
+ }
659
+ ],
660
+ "baseUrl": ".",
661
+ "paths": {
662
+ "@/*": ["./src/*"]
663
+ }
664
+ },
665
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
666
+ "exclude": ["node_modules"]
667
+ }`;
668
+ const getGithubWorkflow = () => `name: Deploy to Azure
669
+
670
+ on:
671
+ push:
672
+ branches: [ main ]
673
+ workflow_dispatch:
674
+
675
+ jobs:
676
+ build-and-deploy:
677
+ runs-on: ubuntu-latest
678
+
679
+ steps:
680
+ - uses: actions/checkout@v4
681
+
682
+ - name: Setup Node.js
683
+ uses: actions/setup-node@v4
684
+ with:
685
+ node-version: '18'
686
+ cache: 'npm'
687
+
688
+ - name: Install dependencies
689
+ run: npm ci
690
+
691
+ - name: Build application
692
+ run: npm run build
693
+ env:
694
+ NEXTAUTH_URL: \${{ secrets.NEXTAUTH_URL }}
695
+ NEXTAUTH_SECRET: \${{ secrets.NEXTAUTH_SECRET }}
696
+ AZURE_AD_CLIENT_ID: \${{ secrets.AZURE_AD_CLIENT_ID }}
697
+ AZURE_AD_CLIENT_SECRET: \${{ secrets.AZURE_AD_CLIENT_SECRET }}
698
+ AZURE_AD_TENANT_ID: \${{ secrets.AZURE_AD_TENANT_ID }}
699
+ DYNAMICS_BASE_URL: \${{ secrets.DYNAMICS_BASE_URL }}
700
+ DYNAMICS_API_VERSION: \${{ secrets.DYNAMICS_API_VERSION }}
701
+
702
+ - name: Deploy to Azure Web App
703
+ uses: azure/webapps-deploy@v2
704
+ with:
705
+ app-name: \${{ secrets.AZURE_WEBAPP_NAME }}
706
+ publish-profile: \${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
707
+ package: .`;
708
+ const getNextConfig = () => `import type { NextConfig } from 'next'
709
+
710
+ const nextConfig: NextConfig = {
711
+ output: 'standalone',
712
+ experimental: {
713
+ optimizePackageImports: ['@progress/kendo-react-buttons', '@progress/kendo-react-inputs'],
714
+ },
715
+ webpack: (config) => {
716
+ config.resolve.alias = {
717
+ ...config.resolve.alias,
718
+ }
719
+ return config
720
+ },
721
+ }
722
+
723
+ export default nextConfig`;
724
+ export const createPortalApp = async (projectName) => {
725
+ // Prompt for UI library choice
726
+ const { uiLibrary } = await inquirer.prompt([
727
+ {
728
+ type: "list",
729
+ name: "uiLibrary",
730
+ message: "Which UI library would you like to use?",
731
+ choices: [
732
+ { name: "Kendo UI (React components with themes)", value: "kendo" },
733
+ { name: "Shadcn/ui (Modern React components)", value: "shadcn" },
734
+ ],
735
+ },
736
+ ]);
737
+ let kendoThemePackage = "";
738
+ if (uiLibrary === "kendo") {
739
+ const response = await inquirer.prompt([
740
+ {
741
+ type: "list",
742
+ name: "kendoThemePackage",
743
+ message: "Which Kendo UI theme would you like to install?",
744
+ choices: [
745
+ { name: "Default", value: "@progress/kendo-theme-default" },
746
+ { name: "Bootstrap (v5)", value: "@progress/kendo-theme-bootstrap" },
747
+ { name: "Material (v3)", value: "@progress/kendo-theme-material" },
748
+ { name: "Fluent", value: "@progress/kendo-theme-fluent" },
749
+ { name: "Classic", value: "@progress/kendo-theme-classic" },
750
+ ],
751
+ },
752
+ ]);
753
+ kendoThemePackage = response.kendoThemePackage;
754
+ }
755
+ // Ensure the project directory exists
756
+ const projectDir = path.resolve(process.cwd(), projectName);
757
+ await fs.ensureDir(projectDir);
758
+ console.log(`\nScaffolding a new project in ${chalk.green(projectDir)}...\n`);
759
+ // Create Next.js project with TypeScript
760
+ runCommand(`npx create-next-app@latest ${projectName} --typescript --tailwind --eslint --app --src-dir --import-alias "@/*" --turbopack`, "Creating Next.js + TypeScript project...");
761
+ process.chdir(projectDir);
762
+ // Install additional dependencies based on UI library choice
763
+ let dependencies = ["@tanstack/react-query", "zustand", "next-auth@beta"];
764
+ let devDependencies = [];
765
+ if (uiLibrary === "kendo") {
766
+ dependencies.push("@progress/kendo-react-buttons", "@progress/kendo-react-layout", "@progress/kendo-react-inputs", "@progress/kendo-react-grid", "@progress/kendo-react-dateinputs", "@progress/kendo-react-dropdowns", "@progress/kendo-react-dialogs", "@progress/kendo-licensing", kendoThemePackage);
767
+ }
768
+ else {
769
+ devDependencies.push("tailwindcss-animate");
770
+ }
771
+ const installMessage = uiLibrary === "kendo"
772
+ ? `Installing dependencies (Kendo Theme: ${kendoThemePackage.split("/")[1]})...`
773
+ : "Installing dependencies (Shadcn/ui)...";
774
+ runCommand(`npm install ${dependencies.join(" ")}`, installMessage);
775
+ if (devDependencies.length > 0) {
776
+ runCommand(`npm install -D ${devDependencies.join(" ")}`, "Installing dev dependencies...");
777
+ }
778
+ // Update package.json with Portal-specific scripts
779
+ const packageJsonPath = path.join(projectDir, "package.json");
780
+ const packageJson = await fs.readJson(packageJsonPath);
781
+ if (!packageJson.scripts) {
782
+ packageJson.scripts = {};
783
+ }
784
+ packageJson.scripts["export"] = "next build";
785
+ packageJson.scripts["serve"] = "npx serve@latest dist";
786
+ await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
787
+ ora("Updated package.json with Portal-specific scripts").succeed();
788
+ if (uiLibrary === "kendo") {
789
+ // Create Kendo Tailwind preset
790
+ const kendoPresetPath = path.join(projectDir, "kendo-tw-preset.js");
791
+ await fs.writeFile(kendoPresetPath, getKendoTailwindPreset(), "utf8");
792
+ ora("Created kendo-tw-preset.js").succeed();
793
+ // Replace Tailwind config
794
+ const tailwindConfigPath = path.join(projectDir, "tailwind.config.js");
795
+ await fs.writeFile(tailwindConfigPath, getTailwindConfig(), "utf8");
796
+ ora("Updated tailwind.config.js with Kendo preset").succeed();
797
+ // Replace app layout
798
+ const layoutPath = path.join(projectDir, "src", "app", "layout.tsx");
799
+ await fs.writeFile(layoutPath, getLayoutTemplate(kendoThemePackage), "utf8");
800
+ ora("Updated app layout with Kendo theme import").succeed();
801
+ // Create providers component
802
+ const providersPath = path.join(projectDir, "src", "app", "providers.tsx");
803
+ await fs.writeFile(providersPath, getProvidersTemplate(), "utf8");
804
+ ora("Created providers component").succeed();
805
+ // Replace page template
806
+ const pagePath = path.join(projectDir, "src", "app", "page.tsx");
807
+ await fs.writeFile(pagePath, getPageTemplate(), "utf8");
808
+ ora("Updated home page with Portal integration").succeed();
809
+ // Update globals.css
810
+ const globalsCssPath = path.join(projectDir, "src", "app", "globals.css");
811
+ await fs.writeFile(globalsCssPath, getGlobalsCSS(), "utf8");
812
+ ora("Updated globals.css").succeed();
813
+ }
814
+ else {
815
+ // Shadcn/ui setup
816
+ runCommand("npx shadcn@latest init --force --silent --yes --base-color neutral", "Setting up Shadcn/ui...");
817
+ runCommand("npx shadcn@latest add --all", "Installing all Shadcn/ui components...");
818
+ // Replace app layout
819
+ const layoutPath = path.join(projectDir, "src", "app", "layout.tsx");
820
+ await fs.writeFile(layoutPath, getShadcnLayoutTemplate(), "utf8");
821
+ ora("Updated app layout for Shadcn/ui").succeed();
822
+ // Create providers component
823
+ const providersPath = path.join(projectDir, "src", "app", "providers.tsx");
824
+ await fs.writeFile(providersPath, getShadcnProvidersTemplate(), "utf8");
825
+ ora("Created providers component").succeed();
826
+ // Replace page template
827
+ const pagePath = path.join(projectDir, "src", "app", "page.tsx");
828
+ await fs.writeFile(pagePath, getShadcnPageTemplate(), "utf8");
829
+ ora("Updated home page with Shadcn/ui integration").succeed();
830
+ }
831
+ // Add .prettierrc
832
+ const prettierRcPath = path.join(projectDir, ".prettierrc");
833
+ await fs.writeFile(prettierRcPath, getPrettierConfig(), "utf8");
834
+ ora("Added .prettierrc configuration").succeed();
835
+ // Add Compiler Options
836
+ const tsConfigPath = path.join(projectDir, "tsconfig.json");
837
+ await fs.writeFile(tsConfigPath, compilerOptions(), "utf8");
838
+ ora("Added TypeScript compiler options").succeed();
839
+ // Add .env.local
840
+ const envLocalPath = path.join(projectDir, ".env.local");
841
+ await fs.writeFile(envLocalPath, getEnvConfig(), "utf8");
842
+ ora("Added .env.local configuration").succeed();
843
+ runCommand("npx auth secret", "Adding secret to .env.local");
844
+ // Add auth config file
845
+ const authConfigPath = path.join(projectDir, "auth.ts");
846
+ await fs.writeFile(authConfigPath, authConfig(), "utf8");
847
+ ora("Added auth.ts configuration").succeed();
848
+ // Add NextAuth route handler
849
+ const authRouteDir = path.join(projectDir, "src", "app", "api", "auth", "[...nextauth]");
850
+ await fs.ensureDir(authRouteDir);
851
+ const authRoutePath = path.join(authRouteDir, "route.ts");
852
+ await fs.writeFile(authRoutePath, getAuthRoute(), "utf8");
853
+ ora("Added NextAuth route handler").succeed();
854
+ // Add Dynamics accounts API route
855
+ const dynamicsAccountsRouteDir = path.join(projectDir, "src", "app", "api", "dynamics", "accounts");
856
+ await fs.ensureDir(dynamicsAccountsRouteDir);
857
+ const dynamicsAccountsRoutePath = path.join(dynamicsAccountsRouteDir, "route.ts");
858
+ await fs.writeFile(dynamicsAccountsRoutePath, getDynamicsAccountsRoute(), "utf8");
859
+ ora("Added Dynamics accounts API route").succeed();
860
+ // Add next.config.ts with standalone settings
861
+ const nextConfigPath = path.join(projectDir, "next.config.ts");
862
+ await fs.writeFile(nextConfigPath, getNextConfig(), "utf8");
863
+ ora("Updated next.config.ts with standalone settings").succeed();
864
+ // Add github workflow for deployment
865
+ const workflowFilePath = path.join(projectDir, "example.deploy.yml");
866
+ await fs.writeFile(workflowFilePath, getGithubWorkflow(), "utf8");
867
+ ora("Added GitHub workflow for Azure deployment").succeed();
868
+ // Add Dynamics lib helper
869
+ const dynamicsLibDir = path.join(projectDir, "src", "lib");
870
+ await fs.ensureDir(dynamicsLibDir);
871
+ const dynamicsLibPath = path.join(dynamicsLibDir, "dynamics.ts");
872
+ await fs.writeFile(dynamicsLibPath, getDynamicsLib(), "utf8");
873
+ ora("Added Dynamics library helper").succeed();
874
+ // Add Dynamics accounts React Query hook
875
+ const hooksDir = path.join(projectDir, "src", "hooks");
876
+ await fs.ensureDir(hooksDir);
877
+ const dynamicsAccountsHookPath = path.join(hooksDir, "useDynamicsAccounts.ts");
878
+ await fs.writeFile(dynamicsAccountsHookPath, getDynamicsAccountsHook(), "utf8");
879
+ ora("Added Dynamics accounts React Query hook").succeed();
880
+ // Initialize Git
881
+ runCommand("git init", "Initializing Git repository...");
882
+ runCommand("git add .", "Staging files for initial commit...");
883
+ runCommand(`git commit -m "Initial commit from create-ec-app"`, "Creating initial commit...");
884
+ };
885
+ //# sourceMappingURL=portal.js.map