create-loadout 1.0.0

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 (72) hide show
  1. package/README.md +154 -0
  2. package/dist/claude-md.d.ts +3 -0
  3. package/dist/claude-md.js +494 -0
  4. package/dist/cli.d.ts +1 -0
  5. package/dist/cli.js +186 -0
  6. package/dist/config.d.ts +3 -0
  7. package/dist/config.js +98 -0
  8. package/dist/create-next.d.ts +1 -0
  9. package/dist/create-next.js +17 -0
  10. package/dist/detect.d.ts +4 -0
  11. package/dist/detect.js +60 -0
  12. package/dist/env.d.ts +3 -0
  13. package/dist/env.js +183 -0
  14. package/dist/generate-readme.d.ts +3 -0
  15. package/dist/generate-readme.js +160 -0
  16. package/dist/index.d.ts +2 -0
  17. package/dist/index.js +3 -0
  18. package/dist/instrumentation.d.ts +3 -0
  19. package/dist/instrumentation.js +95 -0
  20. package/dist/integrations/ai-sdk.d.ts +3 -0
  21. package/dist/integrations/ai-sdk.js +20 -0
  22. package/dist/integrations/clerk.d.ts +2 -0
  23. package/dist/integrations/clerk.js +50 -0
  24. package/dist/integrations/firecrawl.d.ts +2 -0
  25. package/dist/integrations/firecrawl.js +26 -0
  26. package/dist/integrations/index.d.ts +4 -0
  27. package/dist/integrations/index.js +64 -0
  28. package/dist/integrations/inngest.d.ts +2 -0
  29. package/dist/integrations/inngest.js +45 -0
  30. package/dist/integrations/neon-drizzle.d.ts +2 -0
  31. package/dist/integrations/neon-drizzle.js +56 -0
  32. package/dist/integrations/posthog.d.ts +2 -0
  33. package/dist/integrations/posthog.js +25 -0
  34. package/dist/integrations/resend.d.ts +2 -0
  35. package/dist/integrations/resend.js +34 -0
  36. package/dist/integrations/sentry.d.ts +2 -0
  37. package/dist/integrations/sentry.js +47 -0
  38. package/dist/integrations/stripe.d.ts +2 -0
  39. package/dist/integrations/stripe.js +45 -0
  40. package/dist/integrations/uploadthing.d.ts +2 -0
  41. package/dist/integrations/uploadthing.js +34 -0
  42. package/dist/landing-page.d.ts +2 -0
  43. package/dist/landing-page.js +97 -0
  44. package/dist/prompts.d.ts +7 -0
  45. package/dist/prompts.js +99 -0
  46. package/dist/setup-shadcn.d.ts +1 -0
  47. package/dist/setup-shadcn.js +27 -0
  48. package/dist/templates/ai-sdk.d.ts +12 -0
  49. package/dist/templates/ai-sdk.js +96 -0
  50. package/dist/templates/clerk.d.ts +6 -0
  51. package/dist/templates/clerk.js +96 -0
  52. package/dist/templates/firecrawl.d.ts +4 -0
  53. package/dist/templates/firecrawl.js +106 -0
  54. package/dist/templates/inngest.d.ts +6 -0
  55. package/dist/templates/inngest.js +91 -0
  56. package/dist/templates/neon-drizzle.d.ts +16 -0
  57. package/dist/templates/neon-drizzle.js +343 -0
  58. package/dist/templates/posthog.d.ts +3 -0
  59. package/dist/templates/posthog.js +10 -0
  60. package/dist/templates/resend.d.ts +5 -0
  61. package/dist/templates/resend.js +102 -0
  62. package/dist/templates/sentry.d.ts +8 -0
  63. package/dist/templates/sentry.js +145 -0
  64. package/dist/templates/stripe.d.ts +6 -0
  65. package/dist/templates/stripe.js +215 -0
  66. package/dist/templates/uploadthing.d.ts +7 -0
  67. package/dist/templates/uploadthing.js +150 -0
  68. package/dist/templates/zustand.d.ts +3 -0
  69. package/dist/templates/zustand.js +26 -0
  70. package/dist/types.d.ts +26 -0
  71. package/dist/types.js +1 -0
  72. package/package.json +46 -0
@@ -0,0 +1,145 @@
1
+ export const sentryTemplates = {
2
+ instrumentation: `import * as Sentry from '@sentry/nextjs';
3
+
4
+ export async function register() {
5
+ if (process.env.NEXT_RUNTIME === 'nodejs') {
6
+ await import('./sentry.server.config');
7
+ }
8
+
9
+ if (process.env.NEXT_RUNTIME === 'edge') {
10
+ await import('./sentry.edge.config');
11
+ }
12
+ }
13
+
14
+ export const onRequestError = Sentry.captureRequestError;
15
+ `,
16
+ instrumentationClient: `import * as Sentry from '@sentry/nextjs';
17
+ import { SENTRY_DSN } from '@/lib/config';
18
+
19
+ Sentry.init({
20
+ dsn: SENTRY_DSN,
21
+ environment: process.env.NODE_ENV,
22
+ sendDefaultPii: false,
23
+
24
+ tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 0,
25
+ replaysSessionSampleRate: 0.1,
26
+ replaysOnErrorSampleRate: 1.0,
27
+
28
+ integrations: [
29
+ Sentry.replayIntegration({
30
+ maskAllText: true,
31
+ blockAllMedia: true,
32
+ }),
33
+ ],
34
+
35
+ beforeSend(event) {
36
+ if (process.env.NODE_ENV === 'development') return null;
37
+
38
+ if (event.user) {
39
+ delete event.user.email;
40
+ delete event.user.ip_address;
41
+ delete event.user.username;
42
+ }
43
+ if (event.request?.headers) {
44
+ delete event.request.headers['authorization'];
45
+ delete event.request.headers['cookie'];
46
+ }
47
+ return event;
48
+ },
49
+ });
50
+
51
+ export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
52
+ `,
53
+ serverConfig: `import * as Sentry from '@sentry/nextjs';
54
+ import { SENTRY_DSN } from '@/lib/config';
55
+
56
+ Sentry.init({
57
+ dsn: SENTRY_DSN,
58
+ environment: process.env.NODE_ENV,
59
+ sendDefaultPii: false,
60
+
61
+ tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 0,
62
+
63
+ beforeSend(event) {
64
+ if (process.env.NODE_ENV === 'development') return null;
65
+
66
+ if (event.user) {
67
+ delete event.user.email;
68
+ delete event.user.ip_address;
69
+ delete event.user.username;
70
+ }
71
+ if (event.request?.headers) {
72
+ delete event.request.headers['authorization'];
73
+ delete event.request.headers['cookie'];
74
+ }
75
+ return event;
76
+ },
77
+ });
78
+ `,
79
+ edgeConfig: `import * as Sentry from '@sentry/nextjs';
80
+ import { SENTRY_DSN } from '@/lib/config';
81
+
82
+ Sentry.init({
83
+ dsn: SENTRY_DSN,
84
+ environment: process.env.NODE_ENV,
85
+ sendDefaultPii: false,
86
+
87
+ tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 0,
88
+
89
+ beforeSend(event) {
90
+ if (process.env.NODE_ENV === 'development') return null;
91
+
92
+ if (event.user) {
93
+ delete event.user.email;
94
+ delete event.user.ip_address;
95
+ delete event.user.username;
96
+ }
97
+ if (event.request?.headers) {
98
+ delete event.request.headers['authorization'];
99
+ delete event.request.headers['cookie'];
100
+ }
101
+ return event;
102
+ },
103
+ });
104
+ `,
105
+ globalError: `'use client';
106
+
107
+ import * as Sentry from '@sentry/nextjs';
108
+ import NextError from 'next/error';
109
+ import { useEffect } from 'react';
110
+
111
+ export default function GlobalError({
112
+ error,
113
+ }: {
114
+ error: Error & { digest?: string };
115
+ }) {
116
+ useEffect(() => {
117
+ Sentry.captureException(error);
118
+ }, [error]);
119
+
120
+ return (
121
+ <html lang="en">
122
+ <body>
123
+ <NextError statusCode={0} />
124
+ </body>
125
+ </html>
126
+ );
127
+ }
128
+ `,
129
+ nextConfig: `import { withSentryConfig } from '@sentry/nextjs';
130
+ import type { NextConfig } from 'next';
131
+ import { SENTRY_ORG, SENTRY_PROJECT } from './lib/config';
132
+
133
+ const nextConfig: NextConfig = {};
134
+
135
+ export default withSentryConfig(nextConfig, {
136
+ org: SENTRY_ORG,
137
+ project: SENTRY_PROJECT,
138
+ silent: !process.env.CI,
139
+ widenClientFileUpload: true,
140
+ tunnelRoute: '/monitoring',
141
+ hideSourceMaps: true,
142
+ disableLogger: true,
143
+ });
144
+ `,
145
+ };
@@ -0,0 +1,6 @@
1
+ export declare const stripeTemplates: {
2
+ paymentService: string;
3
+ checkoutRoute: string;
4
+ webhooksRoute: string;
5
+ portalRoute: string;
6
+ };
@@ -0,0 +1,215 @@
1
+ export const stripeTemplates = {
2
+ paymentService: `import Stripe from 'stripe';
3
+ import { STRIPE_SECRET_KEY } from '@/lib/config';
4
+
5
+ export interface CreateCheckoutOptions {
6
+ priceId: string;
7
+ customerId?: string;
8
+ mode?: 'payment' | 'subscription';
9
+ successUrl: string;
10
+ cancelUrl: string;
11
+ metadata?: Record<string, string>;
12
+ }
13
+
14
+ export interface CreatePortalOptions {
15
+ customerId: string;
16
+ returnUrl: string;
17
+ }
18
+
19
+ export class PaymentService {
20
+ private stripe: Stripe;
21
+
22
+ constructor(secretKey: string) {
23
+ this.stripe = new Stripe(secretKey, {
24
+ apiVersion: '2024-12-18.acacia',
25
+ typescript: true,
26
+ });
27
+ }
28
+
29
+ async createCheckoutSession(options: CreateCheckoutOptions): Promise<Stripe.Checkout.Session> {
30
+ return this.stripe.checkout.sessions.create({
31
+ mode: options.mode ?? 'subscription',
32
+ payment_method_types: ['card'],
33
+ line_items: [
34
+ {
35
+ price: options.priceId,
36
+ quantity: 1,
37
+ },
38
+ ],
39
+ customer: options.customerId,
40
+ success_url: options.successUrl,
41
+ cancel_url: options.cancelUrl,
42
+ metadata: options.metadata,
43
+ });
44
+ }
45
+
46
+ async createPortalSession(options: CreatePortalOptions): Promise<Stripe.BillingPortal.Session> {
47
+ return this.stripe.billingPortal.sessions.create({
48
+ customer: options.customerId,
49
+ return_url: options.returnUrl,
50
+ });
51
+ }
52
+
53
+ async getSubscription(subscriptionId: string): Promise<Stripe.Subscription> {
54
+ return this.stripe.subscriptions.retrieve(subscriptionId);
55
+ }
56
+
57
+ async cancelSubscription(subscriptionId: string): Promise<Stripe.Subscription> {
58
+ return this.stripe.subscriptions.cancel(subscriptionId);
59
+ }
60
+
61
+ async getCustomer(customerId: string): Promise<Stripe.Customer | Stripe.DeletedCustomer> {
62
+ return this.stripe.customers.retrieve(customerId);
63
+ }
64
+
65
+ async createCustomer(email: string, metadata?: Record<string, string>): Promise<Stripe.Customer> {
66
+ return this.stripe.customers.create({
67
+ email,
68
+ metadata,
69
+ });
70
+ }
71
+
72
+ constructWebhookEvent(
73
+ payload: string | Buffer,
74
+ signature: string,
75
+ webhookSecret: string
76
+ ): Stripe.Event {
77
+ return this.stripe.webhooks.constructEvent(payload, signature, webhookSecret);
78
+ }
79
+ }
80
+
81
+ export const paymentService = new PaymentService(STRIPE_SECRET_KEY);
82
+
83
+ export type { Stripe };
84
+ `,
85
+ checkoutRoute: `import { NextResponse } from 'next/server';
86
+ import { paymentService } from '@/services/payment.service';
87
+ import { z } from 'zod';
88
+
89
+ const checkoutSchema = z.object({
90
+ priceId: z.string(),
91
+ customerId: z.string().optional(),
92
+ successUrl: z.url(),
93
+ cancelUrl: z.url(),
94
+ });
95
+
96
+ export async function POST(req: Request) {
97
+ try {
98
+ const body = await req.json();
99
+ const { priceId, customerId, successUrl, cancelUrl } = checkoutSchema.parse(body);
100
+
101
+ const session = await paymentService.createCheckoutSession({
102
+ priceId,
103
+ customerId,
104
+ successUrl,
105
+ cancelUrl,
106
+ });
107
+
108
+ return NextResponse.json({ url: session.url });
109
+ } catch (error) {
110
+ if (error instanceof z.ZodError) {
111
+ return NextResponse.json(
112
+ { error: 'Invalid request', details: error.errors },
113
+ { status: 400 }
114
+ );
115
+ }
116
+ return NextResponse.json(
117
+ { error: error instanceof Error ? error.message : 'Failed to create checkout' },
118
+ { status: 500 }
119
+ );
120
+ }
121
+ }
122
+ `,
123
+ webhooksRoute: `import { headers } from 'next/headers';
124
+ import { NextResponse } from 'next/server';
125
+ import { paymentService, type Stripe } from '@/services/payment.service';
126
+ import { STRIPE_WEBHOOK_SECRET } from '@/lib/config';
127
+
128
+ export async function POST(req: Request) {
129
+ const body = await req.text();
130
+ const headersList = await headers();
131
+ const signature = headersList.get('stripe-signature')!;
132
+
133
+ let event: Stripe.Event;
134
+
135
+ try {
136
+ event = paymentService.constructWebhookEvent(body, signature, STRIPE_WEBHOOK_SECRET);
137
+ } catch (error) {
138
+ console.error('Webhook signature verification failed:', error);
139
+ return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });
140
+ }
141
+
142
+ switch (event.type) {
143
+ case 'checkout.session.completed': {
144
+ const session = event.data.object as Stripe.Checkout.Session;
145
+ console.log('Checkout completed:', session.id);
146
+ break;
147
+ }
148
+
149
+ case 'customer.subscription.created':
150
+ case 'customer.subscription.updated': {
151
+ const subscription = event.data.object as Stripe.Subscription;
152
+ console.log('Subscription updated:', subscription.id, subscription.status);
153
+ break;
154
+ }
155
+
156
+ case 'customer.subscription.deleted': {
157
+ const subscription = event.data.object as Stripe.Subscription;
158
+ console.log('Subscription cancelled:', subscription.id);
159
+ break;
160
+ }
161
+
162
+ case 'invoice.payment_succeeded': {
163
+ const invoice = event.data.object as Stripe.Invoice;
164
+ console.log('Payment succeeded:', invoice.id);
165
+ break;
166
+ }
167
+
168
+ case 'invoice.payment_failed': {
169
+ const invoice = event.data.object as Stripe.Invoice;
170
+ console.log('Payment failed:', invoice.id);
171
+ break;
172
+ }
173
+
174
+ default:
175
+ console.log('Unhandled event type:', event.type);
176
+ }
177
+
178
+ return NextResponse.json({ received: true });
179
+ }
180
+ `,
181
+ portalRoute: `import { NextResponse } from 'next/server';
182
+ import { paymentService } from '@/services/payment.service';
183
+ import { z } from 'zod';
184
+
185
+ const portalSchema = z.object({
186
+ customerId: z.string(),
187
+ returnUrl: z.url(),
188
+ });
189
+
190
+ export async function POST(req: Request) {
191
+ try {
192
+ const body = await req.json();
193
+ const { customerId, returnUrl } = portalSchema.parse(body);
194
+
195
+ const session = await paymentService.createPortalSession({
196
+ customerId,
197
+ returnUrl,
198
+ });
199
+
200
+ return NextResponse.json({ url: session.url });
201
+ } catch (error) {
202
+ if (error instanceof z.ZodError) {
203
+ return NextResponse.json(
204
+ { error: 'Invalid request', details: error.errors },
205
+ { status: 400 }
206
+ );
207
+ }
208
+ return NextResponse.json(
209
+ { error: error instanceof Error ? error.message : 'Failed to create portal session' },
210
+ { status: 500 }
211
+ );
212
+ }
213
+ }
214
+ `,
215
+ };
@@ -0,0 +1,7 @@
1
+ export declare const uploadthingTemplates: {
2
+ uploadthingClient: string;
3
+ fileService: string;
4
+ uploadthingCore: string;
5
+ uploadthingRoute: string;
6
+ uploadButton: string;
7
+ };
@@ -0,0 +1,150 @@
1
+ export const uploadthingTemplates = {
2
+ uploadthingClient: `import {
3
+ generateUploadButton,
4
+ generateUploadDropzone,
5
+ generateReactHelpers,
6
+ } from '@uploadthing/react';
7
+
8
+ import type { OurFileRouter } from '@/app/api/uploadthing/core';
9
+
10
+ export const UploadButton = generateUploadButton<OurFileRouter>();
11
+ export const UploadDropzone = generateUploadDropzone<OurFileRouter>();
12
+ export const { useUploadThing } = generateReactHelpers<OurFileRouter>();
13
+ `,
14
+ fileService: `import { UTApi } from 'uploadthing/server';
15
+
16
+ export interface UploadedFile {
17
+ key: string;
18
+ url: string;
19
+ name: string;
20
+ size: number;
21
+ }
22
+
23
+ export class FileService {
24
+ constructor(private utapi: UTApi) {}
25
+
26
+ async deleteFile(key: string): Promise<void> {
27
+ await this.utapi.deleteFiles(key);
28
+ }
29
+
30
+ async deleteFiles(keys: string[]): Promise<void> {
31
+ await this.utapi.deleteFiles(keys);
32
+ }
33
+
34
+ async getFileUrls(keys: string[]): Promise<string[]> {
35
+ const result = await this.utapi.getFileUrls(keys);
36
+ return result.data.map((f) => f.url);
37
+ }
38
+
39
+ async renameFile(key: string, newName: string): Promise<void> {
40
+ await this.utapi.renameFiles({ fileKey: key, newName });
41
+ }
42
+ }
43
+
44
+ export const fileService = new FileService(new UTApi());
45
+ `,
46
+ uploadthingCore: `import { createUploadthing, type FileRouter } from 'uploadthing/next';
47
+ import { UploadThingError } from 'uploadthing/server';
48
+
49
+ const f = createUploadthing();
50
+
51
+ const auth = (req: Request) => ({ id: 'user-id' });
52
+
53
+ export const ourFileRouter = {
54
+ imageUploader: f({
55
+ image: {
56
+ maxFileSize: '4MB',
57
+ maxFileCount: 4,
58
+ },
59
+ })
60
+ .middleware(async ({ req }) => {
61
+ const user = await auth(req);
62
+ if (!user) throw new UploadThingError('Unauthorized');
63
+ return { userId: user.id };
64
+ })
65
+ .onUploadComplete(async ({ metadata, file }) => {
66
+ console.log('Upload complete for userId:', metadata.userId);
67
+ console.log('File URL:', file.ufsUrl);
68
+ return { uploadedBy: metadata.userId, url: file.ufsUrl };
69
+ }),
70
+
71
+ documentUploader: f({
72
+ pdf: { maxFileSize: '16MB' },
73
+ 'application/msword': { maxFileSize: '16MB' },
74
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': {
75
+ maxFileSize: '16MB',
76
+ },
77
+ })
78
+ .middleware(async ({ req }) => {
79
+ const user = await auth(req);
80
+ if (!user) throw new UploadThingError('Unauthorized');
81
+ return { userId: user.id };
82
+ })
83
+ .onUploadComplete(async ({ metadata, file }) => {
84
+ return { uploadedBy: metadata.userId, url: file.ufsUrl };
85
+ }),
86
+ } satisfies FileRouter;
87
+
88
+ export type OurFileRouter = typeof ourFileRouter;
89
+ `,
90
+ uploadthingRoute: `import { createRouteHandler } from 'uploadthing/next';
91
+ import { ourFileRouter } from './core';
92
+
93
+ export const { GET, POST } = createRouteHandler({
94
+ router: ourFileRouter,
95
+ });
96
+ `,
97
+ uploadButton: `'use client';
98
+
99
+ import { UploadButton, UploadDropzone } from '@/lib/uploadthing.client';
100
+ import { useState } from 'react';
101
+
102
+ interface UploadedFile {
103
+ url: string;
104
+ name: string;
105
+ }
106
+
107
+ export function ImageUploadButton({
108
+ onUpload,
109
+ }: {
110
+ onUpload?: (files: UploadedFile[]) => void;
111
+ }) {
112
+ const [isUploading, setIsUploading] = useState(false);
113
+
114
+ return (
115
+ <UploadButton
116
+ endpoint="imageUploader"
117
+ onUploadBegin={() => setIsUploading(true)}
118
+ onClientUploadComplete={(res) => {
119
+ setIsUploading(false);
120
+ const files = res.map((f) => ({ url: f.ufsUrl, name: f.name }));
121
+ onUpload?.(files);
122
+ }}
123
+ onUploadError={(error: Error) => {
124
+ setIsUploading(false);
125
+ console.error('Upload error:', error.message);
126
+ }}
127
+ />
128
+ );
129
+ }
130
+
131
+ export function ImageUploadDropzone({
132
+ onUpload,
133
+ }: {
134
+ onUpload?: (files: UploadedFile[]) => void;
135
+ }) {
136
+ return (
137
+ <UploadDropzone
138
+ endpoint="imageUploader"
139
+ onClientUploadComplete={(res) => {
140
+ const files = res.map((f) => ({ url: f.ufsUrl, name: f.name }));
141
+ onUpload?.(files);
142
+ }}
143
+ onUploadError={(error: Error) => {
144
+ console.error('Upload error:', error.message);
145
+ }}
146
+ />
147
+ );
148
+ }
149
+ `,
150
+ };
@@ -0,0 +1,3 @@
1
+ export declare const zustandTemplates: {
2
+ exampleStore: string;
3
+ };
@@ -0,0 +1,26 @@
1
+ export const zustandTemplates = {
2
+ exampleStore: `import { createStore, useStore } from 'zustand';
3
+
4
+ interface CounterState {
5
+ count: number;
6
+ increment: () => void;
7
+ decrement: () => void;
8
+ reset: () => void;
9
+ }
10
+
11
+ const initialState = {
12
+ count: 0,
13
+ };
14
+
15
+ export const counterStore = createStore<CounterState>()((set) => ({
16
+ ...initialState,
17
+ increment: () => set((state) => ({ count: state.count + 1 })),
18
+ decrement: () => set((state) => ({ count: state.count - 1 })),
19
+ reset: () => set(initialState),
20
+ }));
21
+
22
+ export const useCounterStore = <T>(selector: (state: CounterState) => T): T => {
23
+ return useStore(counterStore, selector);
24
+ };
25
+ `,
26
+ };
@@ -0,0 +1,26 @@
1
+ export type IntegrationId = 'clerk' | 'neon-drizzle' | 'ai-sdk' | 'resend' | 'firecrawl' | 'inngest' | 'uploadthing' | 'stripe' | 'posthog' | 'sentry';
2
+ export interface Integration {
3
+ id: IntegrationId;
4
+ name: string;
5
+ description: string;
6
+ packages: string[];
7
+ devPackages?: string[];
8
+ envVars: EnvVar[];
9
+ setup: (projectPath: string) => Promise<void>;
10
+ }
11
+ export interface EnvVar {
12
+ key: string;
13
+ description: string;
14
+ example: string;
15
+ isPublic?: boolean;
16
+ }
17
+ export type AIProviderChoice = 'openai' | 'anthropic' | 'google';
18
+ export interface ProjectConfig {
19
+ name: string;
20
+ integrations: IntegrationId[];
21
+ aiProvider?: AIProviderChoice;
22
+ }
23
+ export interface TemplateFile {
24
+ path: string;
25
+ content: string;
26
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "create-loadout",
3
+ "version": "1.0.0",
4
+ "description": "Custom Next.js scaffolding CLI with optional SaaS integrations",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-loadout": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "dev": "tsx src/index.ts",
15
+ "prepublishOnly": "npm run build"
16
+ },
17
+ "keywords": [
18
+ "nextjs",
19
+ "cli",
20
+ "scaffolding",
21
+ "saas",
22
+ "boilerplate"
23
+ ],
24
+ "author": {
25
+ "name": "Kyle Davidson"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/kylerd/loadout.git"
30
+ },
31
+ "license": "MIT",
32
+ "dependencies": {
33
+ "@inquirer/prompts": "^7.0.0",
34
+ "chalk": "^5.0.0",
35
+ "ora": "^8.0.0",
36
+ "execa": "^9.0.0"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^22.0.0",
40
+ "typescript": "^5.0.0",
41
+ "tsx": "^4.0.0"
42
+ },
43
+ "engines": {
44
+ "node": ">=18.0.0"
45
+ }
46
+ }