@asynx/create-asynx-next-app 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 (60) hide show
  1. package/CONTRIBUTING.md +104 -0
  2. package/LICENSE +21 -0
  3. package/README.md +111 -0
  4. package/dist/cli.js +56 -0
  5. package/dist/commands/create.js +32 -0
  6. package/dist/constants/template.js +17 -0
  7. package/dist/index.js +3 -0
  8. package/dist/utils/copy.js +16 -0
  9. package/dist/utils/exec.js +16 -0
  10. package/dist/utils/install.js +14 -0
  11. package/dist/utils/logger.js +7 -0
  12. package/dist/utils/package-manager.js +15 -0
  13. package/dist/utils/package.js +8 -0
  14. package/dist/utils/path.js +2 -0
  15. package/package.json +45 -0
  16. package/src/cli.ts +74 -0
  17. package/src/commands/create.ts +39 -0
  18. package/src/constants/template.ts +19 -0
  19. package/src/index.ts +5 -0
  20. package/src/utils/copy.ts +19 -0
  21. package/src/utils/exec.ts +20 -0
  22. package/src/utils/install.ts +18 -0
  23. package/src/utils/logger.ts +8 -0
  24. package/src/utils/package-manager.ts +17 -0
  25. package/src/utils/package.ts +14 -0
  26. package/src/utils/path.ts +4 -0
  27. package/templates/moderate/next-env.d.ts +4 -0
  28. package/templates/moderate/package.json +20 -0
  29. package/templates/moderate/src/app/globals.css +8 -0
  30. package/templates/moderate/src/app/layout.tsx +18 -0
  31. package/templates/moderate/src/app/page.tsx +8 -0
  32. package/templates/moderate/tsconfig.json +20 -0
  33. package/templates/moderate/utils/async.ts +3 -0
  34. package/templates/moderate/utils/date.ts +17 -0
  35. package/templates/moderate/utils/index.ts +4 -0
  36. package/templates/moderate/utils/number.ts +6 -0
  37. package/templates/moderate/utils/string.ts +7 -0
  38. package/templates/saas/next-env.d.ts +4 -0
  39. package/templates/saas/package.json +20 -0
  40. package/templates/saas/src/app/globals.css +8 -0
  41. package/templates/saas/src/app/layout.tsx +18 -0
  42. package/templates/saas/src/app/page.tsx +8 -0
  43. package/templates/saas/tsconfig.json +20 -0
  44. package/templates/saas/utils/async.ts +3 -0
  45. package/templates/saas/utils/date.ts +17 -0
  46. package/templates/saas/utils/index.ts +4 -0
  47. package/templates/saas/utils/number.ts +6 -0
  48. package/templates/saas/utils/string.ts +7 -0
  49. package/templates/simple/next-env.d.ts +4 -0
  50. package/templates/simple/package.json +20 -0
  51. package/templates/simple/src/app/globals.css +8 -0
  52. package/templates/simple/src/app/layout.tsx +18 -0
  53. package/templates/simple/src/app/page.tsx +8 -0
  54. package/templates/simple/tsconfig.json +20 -0
  55. package/templates/simple/utils/async.ts +3 -0
  56. package/templates/simple/utils/date.ts +17 -0
  57. package/templates/simple/utils/index.ts +4 -0
  58. package/templates/simple/utils/number.ts +6 -0
  59. package/templates/simple/utils/string.ts +7 -0
  60. package/tsconfig.json +13 -0
@@ -0,0 +1,104 @@
1
+ # Contributing to create-asynx-next-app
2
+
3
+ Thank you for considering contributing ❤️ We appreciate your help in making this project better.
4
+
5
+ We welcome contributions in the form of:
6
+
7
+ * 🐛 Bug fixes
8
+ * ✨ Template improvements
9
+ * 🆕 New templates
10
+ * 📚 Documentation improvements
11
+
12
+ ---
13
+
14
+ ## 🛠 Development Setup
15
+
16
+
17
+ To get started with development, clone the repository and install dependencies:
18
+
19
+
20
+ ```bash
21
+ git clone [https://github.com/Asynx-Pvt-Ltd/create-asynx-next-app.git](https://github.com/Asynx-Pvt-Ltd/create-asynx-next-app.git)
22
+ cd create-asynx-next-app
23
+ ```
24
+
25
+ ```bash
26
+ pnpm install
27
+ pnpm dev
28
+ ```
29
+
30
+ ## 📦 Templates
31
+ The core project templates live in the templates/ directory:
32
+
33
+ ```bash
34
+ templates/
35
+ ├─ simple/
36
+ ├─ moderate/
37
+ └─ saas/
38
+ ```
39
+
40
+ Each template must adhere to the following rules:
41
+
42
+ - Be a valid Next.js App Router project.
43
+ - Use TypeScript.
44
+ - Avoid external runtime dependencies on Asynx packages.
45
+
46
+ ## 🧪 Testing changes
47
+ Before opening a Pull Request, please ensure your changes work by building the CLI and testing it locally:
48
+
49
+ ```bash
50
+ pnpm build
51
+ node dist/index.js test-app
52
+ ```
53
+
54
+ Verify the generated application in the test-app directory runs correctly.
55
+
56
+ ## 📌 Guidelines
57
+ When writing code, please follow these principles:
58
+
59
+ - Keep code readable.
60
+ - Prefer clarity over cleverness.
61
+ - Avoid unnecessary abstractions.
62
+
63
+ Follow the existing structure of the codebase.
64
+
65
+ ## 📬 Pull Requests
66
+ When submitting a Pull Request (PR):
67
+
68
+ - Describe what you changed.
69
+ - Explain why it’s useful.
70
+ - Keep PRs focused on a single feature or fix.
71
+
72
+ Thanks for helping improve the project 🚀
73
+
74
+
75
+ ## `CODE_OF_CONDUCT.md`
76
+
77
+ ---
78
+
79
+ # Code of Conduct
80
+
81
+ This project is governed by a standard open-source code of conduct. We are committed to fostering a welcoming and inclusive environment.
82
+
83
+ We expect all contributors to adhere to the following guidelines:
84
+
85
+ * Be **respectful** of differing viewpoints and experiences.
86
+ * Be **inclusive** and encourage participation from everyone.
87
+ * Be **constructive** in all communication and feedback.
88
+
89
+ Harassment, discrimination, or abusive behavior will not be tolerated.
90
+
91
+ ---
92
+
93
+ ## Enforcement
94
+
95
+ Project maintainers reserve the right to review, edit, remove, or block comments, commits, code, and contributors who violate this code of conduct.
96
+
97
+ ---
98
+
99
+ ## Contact
100
+
101
+ If you experience or witness unacceptable behavior, or have any other concerns, please contact:
102
+
103
+ Asynx Devs Pvt Ltd
104
+ 🌐 **Website:** https://asynx.in
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Asynx Devs Pvt Ltd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,111 @@
1
+ # create-asynx-next-app
2
+
3
+ > Opinionated Next.js App Router scaffolder by **Asynx Devs Pvt Ltd**
4
+
5
+ A modern, open-source CLI to scaffold **scalable Next.js applications** using production-ready templates — from simple landing pages to complex SaaS apps.
6
+
7
+ -----
8
+
9
+ ## ✨ Features
10
+
11
+ * ✅ **Next.js (latest)** — App Router only
12
+ * ✅ **TypeScript by default**
13
+ * ✅ **Multiple templates**
14
+ * Simple (Landing pages, MVPs)
15
+ * Moderate (Dashboards, content platforms)
16
+ * SaaS (Scalable, domain-driven architecture)
17
+ * ✅ **Predefined utilities included**
18
+ * ✅ **No vendor lock-in**
19
+ * ✅ **`pnpm` / `npm` / `npx` supported**
20
+ * ✅ **Cross-platform (Windows, macOS, Linux)**
21
+
22
+ -----
23
+
24
+ ## 🚀 Usage
25
+
26
+ ### Using pnpm (recommended)
27
+
28
+ ```bash
29
+ pnpm create asynx-next-app my-app
30
+ ```
31
+
32
+ ### Using npx
33
+
34
+ ```bash
35
+ npx create-asynx-next-app my-app
36
+ ```
37
+
38
+ -----
39
+
40
+ ## 🧩 Templates
41
+
42
+ | Template | Best for: |
43
+ | :--- | :--- |
44
+ | **Simple App** | Landing pages, Small tools, MVPs |
45
+ | **Moderate App** | Dashboards, Content platforms, Early-stage startups |
46
+ | **SaaS / Complex App** | SaaS products, Multi-tenant systems, Role-based platforms |
47
+
48
+ -----
49
+
50
+ ## 📁 Project Structure
51
+
52
+ All generated projects follow this structure:
53
+
54
+ ```
55
+ src/
56
+ ├─ app/
57
+ ├─ components/
58
+ ├─ lib/
59
+ ├─ utils/
60
+ └─ types/
61
+ ```
62
+
63
+ With route groups such as:
64
+
65
+ * `(marketing)`
66
+ * `(auth)`
67
+ * `(dashboard)`
68
+
69
+ -----
70
+
71
+ ## 🛠 Included Utilities
72
+
73
+ Each project includes commonly used helpers:
74
+
75
+ * Date & time formatting
76
+ * Relative time helpers
77
+ * Number & currency formatting
78
+ * String utilities
79
+ * Async helpers
80
+
81
+ > 💡 **Note:** Utilities are copied into your project — you fully own the code.
82
+
83
+ -----
84
+
85
+ ## 🧠 Philosophy
86
+
87
+ * This is **not a framework**.
88
+ * No runtime dependencies on Asynx packages
89
+ * No hidden abstractions
90
+ * No lock-in
91
+ * Clean, understandable code
92
+
93
+ > You can delete this CLI after creation — your project will continue to work.
94
+
95
+ -----
96
+
97
+ ## 🤝 Contributing
98
+
99
+ Contributions are welcome\! Please read:
100
+
101
+ * [CONTRIBUTING.md](https://www.google.com/search?q=CONTRIBUTING.md)
102
+ * [CODE\_OF\_CONDUCT.md](https://www.google.com/search?q=CODE_OF_CONDUCT.md)
103
+
104
+ -----
105
+
106
+ ## 🏢 About Asynx Devs Pvt Ltd
107
+
108
+ Asynx Devs builds scalable web products, internal tools, and SaaS platforms.
109
+
110
+ * 🌐 **Website:** [https://asynx.in](https://asynx.in)
111
+ * 🐙 **GitHub:** [https://github.com/Asynx-Pvt-Ltd](https://github.com/Asynx-Pvt-Ltd)
package/dist/cli.js ADDED
@@ -0,0 +1,56 @@
1
+ import pc from 'picocolors';
2
+ import prompts from 'prompts';
3
+ import { createApp } from './commands/create.js';
4
+ import { TEMPLATES } from './constants/template.js';
5
+ import { logger } from './utils/logger.js';
6
+ function parseArgs() {
7
+ const args = process.argv.slice(2);
8
+ return {
9
+ projectName: args[0],
10
+ template: args.includes('--simple')
11
+ ? 'simple'
12
+ : args.includes('--moderate')
13
+ ? 'moderate'
14
+ : args.includes('--saas')
15
+ ? 'saas'
16
+ : undefined,
17
+ };
18
+ }
19
+ export async function runCli() {
20
+ console.clear();
21
+ console.log(pc.bold(`
22
+ 🚀 Asynx Next.js App Generator
23
+ --------------------------------
24
+ Opinionated Next.js templates for real-world apps.
25
+
26
+ by Asynx Devs Pvt Ltd
27
+ https://asynx.in
28
+ `));
29
+ const args = parseArgs();
30
+ const response = await prompts([
31
+ {
32
+ type: args.projectName ? null : 'text',
33
+ name: 'projectName',
34
+ message: 'What is your project name?',
35
+ initial: 'my-asynx-app',
36
+ validate: (name) => name.trim().length === 0 ? 'Project name is required' : true,
37
+ },
38
+ {
39
+ type: args.template ? null : 'select',
40
+ name: 'template',
41
+ message: 'Select a project template',
42
+ choices: TEMPLATES.map((t) => ({
43
+ title: `${t.title} — ${pc.dim(t.description)}`,
44
+ value: t.value,
45
+ })),
46
+ },
47
+ ], {
48
+ onCancel: () => {
49
+ logger.warn('Operation cancelled');
50
+ process.exit(0);
51
+ },
52
+ });
53
+ const projectName = args.projectName ?? response.projectName;
54
+ const template = args.template ?? response.template;
55
+ await createApp(projectName, template);
56
+ }
@@ -0,0 +1,32 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { copyDir } from '../utils/copy.js';
5
+ import { logger } from '../utils/logger.js';
6
+ import { updatePackageName } from '../utils/package.js';
7
+ import { installDependencies } from '../utils/install.js';
8
+ import { detectPackageManager } from '../utils/package-manager.js';
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+ export async function createApp(projectName, template) {
12
+ const targetDir = path.resolve(process.cwd(), projectName);
13
+ try {
14
+ await fs.access(targetDir);
15
+ logger.error(`Folder "${projectName}" already exists.`);
16
+ process.exit(1);
17
+ }
18
+ catch {
19
+ // directory does not exist → good
20
+ }
21
+ logger.info(`Creating project in ${targetDir}`);
22
+ const templateDir = path.resolve(__dirname, '../../templates', template);
23
+ await copyDir(templateDir, targetDir);
24
+ await updatePackageName(targetDir, projectName);
25
+ const pm = detectPackageManager();
26
+ await installDependencies(targetDir, pm);
27
+ logger.success('Project ready!');
28
+ logger.info('');
29
+ logger.info('Next steps:');
30
+ logger.info(` cd ${projectName}`);
31
+ logger.info(` ${pm} dev`);
32
+ }
@@ -0,0 +1,17 @@
1
+ export const TEMPLATES = [
2
+ {
3
+ value: 'simple',
4
+ title: 'Simple App',
5
+ description: 'Landing pages, MVPs, small tools',
6
+ },
7
+ {
8
+ value: 'moderate',
9
+ title: 'Moderate App',
10
+ description: 'Dashboards, content platforms, startups',
11
+ },
12
+ {
13
+ value: 'saas',
14
+ title: 'SaaS / Complex App',
15
+ description: 'Multi-tenant SaaS, role-based platforms',
16
+ },
17
+ ];
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { runCli } from './cli.js';
3
+ runCli();
@@ -0,0 +1,16 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ export async function copyDir(src, dest) {
4
+ await fs.mkdir(dest, { recursive: true });
5
+ const entries = await fs.readdir(src, { withFileTypes: true });
6
+ for (const entry of entries) {
7
+ const srcPath = path.join(src, entry.name);
8
+ const destPath = path.join(dest, entry.name);
9
+ if (entry.isDirectory()) {
10
+ await copyDir(srcPath, destPath);
11
+ }
12
+ else {
13
+ await fs.copyFile(srcPath, destPath);
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,16 @@
1
+ import { spawn } from 'child_process';
2
+ export function runCommand(command, args, cwd) {
3
+ return new Promise((resolve, reject) => {
4
+ const child = spawn(command, args, {
5
+ cwd,
6
+ stdio: 'inherit',
7
+ shell: true,
8
+ });
9
+ child.on('close', (code) => {
10
+ if (code === 0)
11
+ resolve();
12
+ else
13
+ reject(new Error(`${command} failed`));
14
+ });
15
+ });
16
+ }
@@ -0,0 +1,14 @@
1
+ import { runCommand } from './exec.js';
2
+ import { logger } from './logger.js';
3
+ export async function installDependencies(projectDir, pm) {
4
+ logger.info(`Installing dependencies using ${pm}...`);
5
+ if (pm === 'pnpm') {
6
+ await runCommand('pnpm', ['install'], projectDir);
7
+ }
8
+ else if (pm === 'yarn') {
9
+ await runCommand('yarn', [], projectDir);
10
+ }
11
+ else {
12
+ await runCommand('npm', ['install'], projectDir);
13
+ }
14
+ }
@@ -0,0 +1,7 @@
1
+ import pc from 'picocolors';
2
+ export const logger = {
3
+ info: (msg) => console.log(pc.cyan(msg)),
4
+ success: (msg) => console.log(pc.green(`✔ ${msg}`)),
5
+ warn: (msg) => console.log(pc.yellow(`⚠ ${msg}`)),
6
+ error: (msg) => console.error(pc.red(`✖ ${msg}`)),
7
+ };
@@ -0,0 +1,15 @@
1
+ import { spawnSync } from 'child_process';
2
+ function commandExists(command) {
3
+ const result = spawnSync(command, ['--version'], {
4
+ stdio: 'ignore',
5
+ shell: true,
6
+ });
7
+ return result.status === 0;
8
+ }
9
+ export function detectPackageManager() {
10
+ if (commandExists('pnpm'))
11
+ return 'pnpm';
12
+ if (commandExists('yarn'))
13
+ return 'yarn';
14
+ return 'npm';
15
+ }
@@ -0,0 +1,8 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ export async function updatePackageName(projectDir, projectName) {
4
+ const pkgPath = path.join(projectDir, 'package.json');
5
+ const raw = await fs.readFile(pkgPath, 'utf-8');
6
+ const updated = raw.replace('__PROJECT_NAME__', projectName);
7
+ await fs.writeFile(pkgPath, updated);
8
+ }
@@ -0,0 +1,2 @@
1
+ import path from 'path';
2
+ export const resolvePath = (...paths) => path.resolve(process.cwd(), ...paths);
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@asynx/create-asynx-next-app",
3
+ "version": "1.0.0",
4
+ "description": "Opinionated Next.js App Router scaffolder by Asynx Devs",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-asynx-next-app": "dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "tsx src/index.ts",
12
+ "prepublishOnly": "pnpm build"
13
+ },
14
+ "keywords": [
15
+ "nextjs",
16
+ "cli",
17
+ "scaffolding",
18
+ "",
19
+ "asynx"
20
+ ],
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/Asynx-Pvt-Ltd/create-asynx-next-app.git"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/Asynx-Pvt-Ltd/create-asynx-next-app/issues"
27
+ },
28
+ "homepage": "https://asynx.in",
29
+ "author": "Asynx Devs Pvt Ltd",
30
+ "license": "MIT",
31
+ "packageManager": "pnpm@10.5.2",
32
+ "engines": {
33
+ "node": ">=18"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^25.0.1",
37
+ "@types/prompts": "^2.4.9",
38
+ "tsx": "^4.21.0",
39
+ "typescript": "^5.9.3"
40
+ },
41
+ "dependencies": {
42
+ "picocolors": "^1.1.1",
43
+ "prompts": "^2.4.2"
44
+ }
45
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,74 @@
1
+ import pc from 'picocolors';
2
+ import prompts from 'prompts';
3
+ import { createApp } from './commands/create.js';
4
+ import { TEMPLATES, TemplateType } from './constants/template.js';
5
+ import { logger } from './utils/logger.js';
6
+
7
+ interface CliOptions {
8
+ projectName?: string;
9
+ template?: TemplateType;
10
+ }
11
+
12
+ function parseArgs(): CliOptions {
13
+ const args = process.argv.slice(2);
14
+
15
+ return {
16
+ projectName: args[0],
17
+ template: args.includes('--simple')
18
+ ? 'simple'
19
+ : args.includes('--moderate')
20
+ ? 'moderate'
21
+ : args.includes('--saas')
22
+ ? 'saas'
23
+ : undefined,
24
+ };
25
+ }
26
+
27
+ export async function runCli() {
28
+ console.clear();
29
+
30
+ console.log(
31
+ pc.bold(`
32
+ 🚀 Asynx Next.js App Generator
33
+ --------------------------------
34
+ Opinionated Next.js templates for real-world apps.
35
+
36
+ by Asynx Devs Pvt Ltd
37
+ https://asynx.in
38
+ `),
39
+ );
40
+
41
+ const args = parseArgs();
42
+
43
+ const response = await prompts(
44
+ [
45
+ {
46
+ type: args.projectName ? null : 'text',
47
+ name: 'projectName',
48
+ message: 'What is your project name?',
49
+ initial: 'my-asynx-app',
50
+ validate: (name: string) =>
51
+ name.trim().length === 0 ? 'Project name is required' : true,
52
+ },
53
+ {
54
+ type: args.template ? null : 'select',
55
+ name: 'template',
56
+ message: 'Select a project template',
57
+ choices: TEMPLATES.map((t) => ({
58
+ title: `${t.title} — ${pc.dim(t.description)}`,
59
+ value: t.value,
60
+ })),
61
+ },
62
+ ],
63
+ {
64
+ onCancel: () => {
65
+ logger.warn('Operation cancelled');
66
+ process.exit(0);
67
+ },
68
+ },
69
+ );
70
+
71
+ const projectName = args.projectName ?? response.projectName;
72
+ const template = args.template ?? response.template;
73
+ await createApp(projectName, template);
74
+ }
@@ -0,0 +1,39 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { copyDir } from '../utils/copy.js';
5
+ import { logger } from '../utils/logger.js';
6
+ import { updatePackageName } from '../utils/package.js';
7
+ import { installDependencies } from '../utils/install.js';
8
+ import { detectPackageManager } from '../utils/package-manager.js';
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+
13
+ export async function createApp(projectName: string, template: string) {
14
+ const targetDir = path.resolve(process.cwd(), projectName);
15
+
16
+ try {
17
+ await fs.access(targetDir);
18
+ logger.error(`Folder "${projectName}" already exists.`);
19
+ process.exit(1);
20
+ } catch {
21
+ // directory does not exist → good
22
+ }
23
+
24
+ logger.info(`Creating project in ${targetDir}`);
25
+
26
+ const templateDir = path.resolve(__dirname, '../../templates', template);
27
+
28
+ await copyDir(templateDir, targetDir);
29
+ await updatePackageName(targetDir, projectName);
30
+
31
+ const pm = detectPackageManager();
32
+ await installDependencies(targetDir, pm);
33
+
34
+ logger.success('Project ready!');
35
+ logger.info('');
36
+ logger.info('Next steps:');
37
+ logger.info(` cd ${projectName}`);
38
+ logger.info(` ${pm} dev`);
39
+ }
@@ -0,0 +1,19 @@
1
+ export type TemplateType = 'simple' | 'moderate' | 'saas';
2
+
3
+ export const TEMPLATES = [
4
+ {
5
+ value: 'simple',
6
+ title: 'Simple App',
7
+ description: 'Landing pages, MVPs, small tools',
8
+ },
9
+ {
10
+ value: 'moderate',
11
+ title: 'Moderate App',
12
+ description: 'Dashboards, content platforms, startups',
13
+ },
14
+ {
15
+ value: 'saas',
16
+ title: 'SaaS / Complex App',
17
+ description: 'Multi-tenant SaaS, role-based platforms',
18
+ },
19
+ ] as const;
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runCli } from './cli.js';
4
+
5
+ runCli();
@@ -0,0 +1,19 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+
4
+ export async function copyDir(src: string, dest: string) {
5
+ await fs.mkdir(dest, { recursive: true });
6
+
7
+ const entries = await fs.readdir(src, { withFileTypes: true });
8
+
9
+ for (const entry of entries) {
10
+ const srcPath = path.join(src, entry.name);
11
+ const destPath = path.join(dest, entry.name);
12
+
13
+ if (entry.isDirectory()) {
14
+ await copyDir(srcPath, destPath);
15
+ } else {
16
+ await fs.copyFile(srcPath, destPath);
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,20 @@
1
+ import { spawn } from 'child_process';
2
+
3
+ export function runCommand(
4
+ command: string,
5
+ args: string[],
6
+ cwd: string,
7
+ ): Promise<void> {
8
+ return new Promise((resolve, reject) => {
9
+ const child = spawn(command, args, {
10
+ cwd,
11
+ stdio: 'inherit',
12
+ shell: true,
13
+ });
14
+
15
+ child.on('close', (code) => {
16
+ if (code === 0) resolve();
17
+ else reject(new Error(`${command} failed`));
18
+ });
19
+ });
20
+ }
@@ -0,0 +1,18 @@
1
+ import { runCommand } from './exec.js';
2
+ import { logger } from './logger.js';
3
+ import { PackageManager } from './package-manager.js';
4
+
5
+ export async function installDependencies(
6
+ projectDir: string,
7
+ pm: PackageManager,
8
+ ) {
9
+ logger.info(`Installing dependencies using ${pm}...`);
10
+
11
+ if (pm === 'pnpm') {
12
+ await runCommand('pnpm', ['install'], projectDir);
13
+ } else if (pm === 'yarn') {
14
+ await runCommand('yarn', [], projectDir);
15
+ } else {
16
+ await runCommand('npm', ['install'], projectDir);
17
+ }
18
+ }
@@ -0,0 +1,8 @@
1
+ import pc from 'picocolors';
2
+
3
+ export const logger = {
4
+ info: (msg: string) => console.log(pc.cyan(msg)),
5
+ success: (msg: string) => console.log(pc.green(`✔ ${msg}`)),
6
+ warn: (msg: string) => console.log(pc.yellow(`⚠ ${msg}`)),
7
+ error: (msg: string) => console.error(pc.red(`✖ ${msg}`)),
8
+ };
@@ -0,0 +1,17 @@
1
+ import { spawnSync } from 'child_process';
2
+
3
+ export type PackageManager = 'pnpm' | 'yarn' | 'npm';
4
+
5
+ function commandExists(command: string) {
6
+ const result = spawnSync(command, ['--version'], {
7
+ stdio: 'ignore',
8
+ shell: true,
9
+ });
10
+ return result.status === 0;
11
+ }
12
+
13
+ export function detectPackageManager(): PackageManager {
14
+ if (commandExists('pnpm')) return 'pnpm';
15
+ if (commandExists('yarn')) return 'yarn';
16
+ return 'npm';
17
+ }
@@ -0,0 +1,14 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+
4
+ export async function updatePackageName(
5
+ projectDir: string,
6
+ projectName: string,
7
+ ) {
8
+ const pkgPath = path.join(projectDir, 'package.json');
9
+
10
+ const raw = await fs.readFile(pkgPath, 'utf-8');
11
+ const updated = raw.replace('__PROJECT_NAME__', projectName);
12
+
13
+ await fs.writeFile(pkgPath, updated);
14
+ }
@@ -0,0 +1,4 @@
1
+ import path from 'path';
2
+
3
+ export const resolvePath = (...paths: string[]) =>
4
+ path.resolve(process.cwd(), ...paths);
@@ -0,0 +1,4 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+
4
+ export {};
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "__PROJECT_NAME__",
3
+ "private": true,
4
+ "scripts": {
5
+ "dev": "next dev",
6
+ "build": "next build",
7
+ "start": "next start",
8
+ "lint": "next lint"
9
+ },
10
+ "dependencies": {
11
+ "next": "latest",
12
+ "react": "latest",
13
+ "react-dom": "latest"
14
+ },
15
+ "devDependencies": {
16
+ "typescript": "^5.0.0",
17
+ "@types/react": "^18.0.0",
18
+ "@types/react-dom": "^18.0.0"
19
+ }
20
+ }
@@ -0,0 +1,8 @@
1
+ * {
2
+ box-sizing: border-box;
3
+ }
4
+
5
+ body {
6
+ margin: 0;
7
+ font-family: system-ui, sans-serif;
8
+ }
@@ -0,0 +1,18 @@
1
+ import './globals.css';
2
+
3
+ export const metadata = {
4
+ title: 'Asynx App',
5
+ description: 'Generated by create-asynx-next-app',
6
+ };
7
+
8
+ export default function RootLayout({
9
+ children,
10
+ }: {
11
+ children: React.ReactNode;
12
+ }) {
13
+ return (
14
+ <html lang="en">
15
+ <body>{children}</body>
16
+ </html>
17
+ );
18
+ }
@@ -0,0 +1,8 @@
1
+ export default function HomePage() {
2
+ return (
3
+ <main style={{ padding: '2rem' }}>
4
+ <h1>🚀 Welcome to your Asynx Next.js App</h1>
5
+ <p>This project was generated using create-asynx-next-app.</p>
6
+ </main>
7
+ );
8
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": false,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "forceConsistentCasingInFileNames": true,
9
+ "noEmit": true,
10
+ "esModuleInterop": true,
11
+ "module": "esnext",
12
+ "moduleResolution": "bundler",
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true,
15
+ "jsx": "preserve",
16
+ "incremental": true
17
+ },
18
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
19
+ "exclude": ["node_modules"]
20
+ }
@@ -0,0 +1,3 @@
1
+ export async function sleep(ms: number) {
2
+ return new Promise((resolve) => setTimeout(resolve, ms));
3
+ }
@@ -0,0 +1,17 @@
1
+ export function formatDate(date: Date | string) {
2
+ return new Date(date).toLocaleDateString();
3
+ }
4
+
5
+ export function formatDateLong(date: Date | string) {
6
+ return new Date(date).toLocaleDateString(undefined, {
7
+ year: 'numeric',
8
+ month: 'long',
9
+ day: 'numeric',
10
+ });
11
+ }
12
+
13
+ export function relativeTime(date: Date | string) {
14
+ const diff = Date.now() - new Date(date).getTime();
15
+ const days = Math.floor(diff / (1000 * 60 * 60 * 24));
16
+ return `${days} day(s) ago`;
17
+ }
@@ -0,0 +1,4 @@
1
+ export * from './date';
2
+ export * from './number';
3
+ export * from './string';
4
+ export * from './async';
@@ -0,0 +1,6 @@
1
+ export function formatCurrency(value: number, currency: string = 'USD') {
2
+ return new Intl.NumberFormat(undefined, {
3
+ style: 'currency',
4
+ currency,
5
+ }).format(value);
6
+ }
@@ -0,0 +1,7 @@
1
+ export function capitalize(value: string) {
2
+ return value.charAt(0).toUpperCase() + value.slice(1);
3
+ }
4
+
5
+ export function slugify(value: string) {
6
+ return value.toLowerCase().trim().replace(/\s+/g, '-');
7
+ }
@@ -0,0 +1,4 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+
4
+ export {};
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "__PROJECT_NAME__",
3
+ "private": true,
4
+ "scripts": {
5
+ "dev": "next dev",
6
+ "build": "next build",
7
+ "start": "next start",
8
+ "lint": "next lint"
9
+ },
10
+ "dependencies": {
11
+ "next": "latest",
12
+ "react": "latest",
13
+ "react-dom": "latest"
14
+ },
15
+ "devDependencies": {
16
+ "typescript": "^5.0.0",
17
+ "@types/react": "^18.0.0",
18
+ "@types/react-dom": "^18.0.0"
19
+ }
20
+ }
@@ -0,0 +1,8 @@
1
+ * {
2
+ box-sizing: border-box;
3
+ }
4
+
5
+ body {
6
+ margin: 0;
7
+ font-family: system-ui, sans-serif;
8
+ }
@@ -0,0 +1,18 @@
1
+ import './globals.css';
2
+
3
+ export const metadata = {
4
+ title: 'Asynx App',
5
+ description: 'Generated by create-asynx-next-app',
6
+ };
7
+
8
+ export default function RootLayout({
9
+ children,
10
+ }: {
11
+ children: React.ReactNode;
12
+ }) {
13
+ return (
14
+ <html lang="en">
15
+ <body>{children}</body>
16
+ </html>
17
+ );
18
+ }
@@ -0,0 +1,8 @@
1
+ export default function HomePage() {
2
+ return (
3
+ <main style={{ padding: '2rem' }}>
4
+ <h1>🚀 Welcome to your Asynx Next.js App</h1>
5
+ <p>This project was generated using create-asynx-next-app.</p>
6
+ </main>
7
+ );
8
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": false,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "forceConsistentCasingInFileNames": true,
9
+ "noEmit": true,
10
+ "esModuleInterop": true,
11
+ "module": "esnext",
12
+ "moduleResolution": "bundler",
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true,
15
+ "jsx": "preserve",
16
+ "incremental": true
17
+ },
18
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
19
+ "exclude": ["node_modules"]
20
+ }
@@ -0,0 +1,3 @@
1
+ export async function sleep(ms: number) {
2
+ return new Promise((resolve) => setTimeout(resolve, ms));
3
+ }
@@ -0,0 +1,17 @@
1
+ export function formatDate(date: Date | string) {
2
+ return new Date(date).toLocaleDateString();
3
+ }
4
+
5
+ export function formatDateLong(date: Date | string) {
6
+ return new Date(date).toLocaleDateString(undefined, {
7
+ year: 'numeric',
8
+ month: 'long',
9
+ day: 'numeric',
10
+ });
11
+ }
12
+
13
+ export function relativeTime(date: Date | string) {
14
+ const diff = Date.now() - new Date(date).getTime();
15
+ const days = Math.floor(diff / (1000 * 60 * 60 * 24));
16
+ return `${days} day(s) ago`;
17
+ }
@@ -0,0 +1,4 @@
1
+ export * from './date';
2
+ export * from './number';
3
+ export * from './string';
4
+ export * from './async';
@@ -0,0 +1,6 @@
1
+ export function formatCurrency(value: number, currency: string = 'USD') {
2
+ return new Intl.NumberFormat(undefined, {
3
+ style: 'currency',
4
+ currency,
5
+ }).format(value);
6
+ }
@@ -0,0 +1,7 @@
1
+ export function capitalize(value: string) {
2
+ return value.charAt(0).toUpperCase() + value.slice(1);
3
+ }
4
+
5
+ export function slugify(value: string) {
6
+ return value.toLowerCase().trim().replace(/\s+/g, '-');
7
+ }
@@ -0,0 +1,4 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+
4
+ export {};
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "__PROJECT_NAME__",
3
+ "private": true,
4
+ "scripts": {
5
+ "dev": "next dev",
6
+ "build": "next build",
7
+ "start": "next start",
8
+ "lint": "next lint"
9
+ },
10
+ "dependencies": {
11
+ "next": "latest",
12
+ "react": "latest",
13
+ "react-dom": "latest"
14
+ },
15
+ "devDependencies": {
16
+ "typescript": "^5.0.0",
17
+ "@types/react": "^18.0.0",
18
+ "@types/react-dom": "^18.0.0"
19
+ }
20
+ }
@@ -0,0 +1,8 @@
1
+ * {
2
+ box-sizing: border-box;
3
+ }
4
+
5
+ body {
6
+ margin: 0;
7
+ font-family: system-ui, sans-serif;
8
+ }
@@ -0,0 +1,18 @@
1
+ import './globals.css';
2
+
3
+ export const metadata = {
4
+ title: 'Asynx App',
5
+ description: 'Generated by create-asynx-next-app',
6
+ };
7
+
8
+ export default function RootLayout({
9
+ children,
10
+ }: {
11
+ children: React.ReactNode;
12
+ }) {
13
+ return (
14
+ <html lang="en">
15
+ <body>{children}</body>
16
+ </html>
17
+ );
18
+ }
@@ -0,0 +1,8 @@
1
+ export default function HomePage() {
2
+ return (
3
+ <main style={{ padding: '2rem' }}>
4
+ <h1>🚀 Welcome to your Asynx Next.js App</h1>
5
+ <p>This project was generated using create-asynx-next-app.</p>
6
+ </main>
7
+ );
8
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": false,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "forceConsistentCasingInFileNames": true,
9
+ "noEmit": true,
10
+ "esModuleInterop": true,
11
+ "module": "esnext",
12
+ "moduleResolution": "bundler",
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true,
15
+ "jsx": "preserve",
16
+ "incremental": true
17
+ },
18
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
19
+ "exclude": ["node_modules"]
20
+ }
@@ -0,0 +1,3 @@
1
+ export async function sleep(ms: number) {
2
+ return new Promise((resolve) => setTimeout(resolve, ms));
3
+ }
@@ -0,0 +1,17 @@
1
+ export function formatDate(date: Date | string) {
2
+ return new Date(date).toLocaleDateString();
3
+ }
4
+
5
+ export function formatDateLong(date: Date | string) {
6
+ return new Date(date).toLocaleDateString(undefined, {
7
+ year: 'numeric',
8
+ month: 'long',
9
+ day: 'numeric',
10
+ });
11
+ }
12
+
13
+ export function relativeTime(date: Date | string) {
14
+ const diff = Date.now() - new Date(date).getTime();
15
+ const days = Math.floor(diff / (1000 * 60 * 60 * 24));
16
+ return `${days} day(s) ago`;
17
+ }
@@ -0,0 +1,4 @@
1
+ export * from './date';
2
+ export * from './number';
3
+ export * from './string';
4
+ export * from './async';
@@ -0,0 +1,6 @@
1
+ export function formatCurrency(value: number, currency: string = 'USD') {
2
+ return new Intl.NumberFormat(undefined, {
3
+ style: 'currency',
4
+ currency,
5
+ }).format(value);
6
+ }
@@ -0,0 +1,7 @@
1
+ export function capitalize(value: string) {
2
+ return value.charAt(0).toUpperCase() + value.slice(1);
3
+ }
4
+
5
+ export function slugify(value: string) {
6
+ return value.toLowerCase().trim().replace(/\s+/g, '-');
7
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true
11
+ },
12
+ "include": ["src"]
13
+ }