Skip to content

Commit 5fafbb0

Browse files
committed
Initial commit
0 parents  commit 5fafbb0

27 files changed

+2419
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
dist

create-app.ts

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/* eslint-disable import/no-extraneous-dependencies */
2+
import retry from 'async-retry';
3+
import chalk from 'chalk';
4+
import cpy from 'cpy';
5+
import fs from 'fs';
6+
import os from 'os';
7+
import path from 'path';
8+
import { makeDir } from './helpers/make-dir';
9+
import { tryGitInit } from './helpers/git';
10+
import { install } from './helpers/install';
11+
import { isFolderEmpty } from './helpers/is-folder-empty';
12+
import { getOnline } from './helpers/is-online';
13+
import { isWriteable } from './helpers/is-writeable';
14+
import type { PackageManager } from './helpers/get-pkg-manager';
15+
16+
export class DownloadError extends Error {}
17+
18+
export async function createApp({
19+
appPath,
20+
packageManager,
21+
typescript,
22+
}: {
23+
appPath: string;
24+
packageManager: PackageManager;
25+
typescript?: boolean;
26+
}): Promise<void> {
27+
const template = typescript ? 'typescript' : 'default';
28+
29+
const root = path.resolve(appPath);
30+
31+
if (!(await isWriteable(path.dirname(root)))) {
32+
console.error(
33+
'The application path is not writable, please check folder permissions and try again.'
34+
);
35+
console.error(
36+
'It is likely you do not have write permissions for this folder.'
37+
);
38+
process.exit(1);
39+
}
40+
41+
const appName = path.basename(root);
42+
43+
await makeDir(root);
44+
if (!isFolderEmpty(root, appName)) {
45+
process.exit(1);
46+
}
47+
48+
const useYarn = packageManager === 'yarn';
49+
const isOnline = !useYarn || (await getOnline());
50+
const originalDirectory = process.cwd();
51+
52+
console.log(`Creating a new Next.js app in ${chalk.green(root)}.`);
53+
console.log();
54+
55+
process.chdir(root);
56+
57+
/**
58+
* Otherwise, if an example repository is not provided for cloning, proceed
59+
* by installing from a template.
60+
*/
61+
console.log(chalk.bold(`Using ${packageManager}.`));
62+
/**
63+
* Create a package.json for the new project.
64+
*/
65+
const packageJson = {
66+
name: appName,
67+
version: '0.1.0',
68+
private: true,
69+
scripts: {
70+
dev: 'next dev',
71+
build: 'next build',
72+
start: 'next start',
73+
lint: 'next lint',
74+
},
75+
};
76+
/**
77+
* Write it to disk.
78+
*/
79+
fs.writeFileSync(
80+
path.join(root, 'package.json'),
81+
JSON.stringify(packageJson, null, 2) + os.EOL
82+
);
83+
/**
84+
* These flags will be passed to `install()`.
85+
*/
86+
const installFlags = { packageManager, isOnline };
87+
/**
88+
* Default dependencies.
89+
*/
90+
const dependencies = ['react', 'react-dom', 'next', 'wagmi', 'ethers'];
91+
/**
92+
* Default devDependencies.
93+
*/
94+
const devDependencies = [
95+
'eslint',
96+
'eslint-config-next',
97+
'autoprefixer',
98+
'postcss',
99+
'tailwindcss',
100+
];
101+
/**
102+
* TypeScript projects will have type definitions and other devDependencies.
103+
*/
104+
if (typescript) {
105+
devDependencies.push(
106+
'typescript',
107+
'@types/react',
108+
'@types/node',
109+
'@types/react-dom'
110+
);
111+
}
112+
/**
113+
* Install package.json dependencies if they exist.
114+
*/
115+
if (dependencies.length) {
116+
console.log();
117+
console.log('Installing dependencies:');
118+
for (const dependency of dependencies) {
119+
console.log(`- ${chalk.cyan(dependency)}`);
120+
}
121+
console.log();
122+
123+
await install(root, dependencies, installFlags);
124+
}
125+
/**
126+
* Install package.json devDependencies if they exist.
127+
*/
128+
if (devDependencies.length) {
129+
console.log();
130+
console.log('Installing devDependencies:');
131+
for (const devDependency of devDependencies) {
132+
console.log(`- ${chalk.cyan(devDependency)}`);
133+
}
134+
console.log();
135+
136+
const devInstallFlags = { devDependencies: true, ...installFlags };
137+
await install(root, devDependencies, devInstallFlags);
138+
}
139+
console.log();
140+
/**
141+
* Copy the template files to the target directory.
142+
*/
143+
await cpy('**', root, {
144+
parents: true,
145+
cwd: path.join(__dirname, 'templates', template),
146+
rename: (name) => {
147+
switch (name) {
148+
case 'gitignore':
149+
case 'eslintrc.json': {
150+
return '.'.concat(name);
151+
}
152+
// README.md is ignored by webpack-asset-relocator-loader used by ncc:
153+
// https://github.com/vercel/webpack-asset-relocator-loader/blob/e9308683d47ff507253e37c9bcbb99474603192b/src/asset-relocator.js#L227
154+
case 'README-template.md': {
155+
return 'README.md';
156+
}
157+
default: {
158+
return name;
159+
}
160+
}
161+
},
162+
});
163+
164+
if (tryGitInit(root)) {
165+
console.log('Initialized a git repository.');
166+
console.log();
167+
}
168+
169+
let cdpath: string;
170+
if (path.join(originalDirectory, appName) === appPath) {
171+
cdpath = appName;
172+
} else {
173+
cdpath = appPath;
174+
}
175+
176+
console.log(`${chalk.green('Success!')} Created ${appName} at ${appPath}`);
177+
console.log('Inside that directory, you can run several commands:');
178+
console.log();
179+
console.log(chalk.cyan(` ${packageManager} ${useYarn ? '' : 'run '}dev`));
180+
console.log(' Starts the development server.');
181+
console.log();
182+
console.log(chalk.cyan(` ${packageManager} ${useYarn ? '' : 'run '}build`));
183+
console.log(' Builds the app for production.');
184+
console.log();
185+
console.log(chalk.cyan(` ${packageManager} start`));
186+
console.log(' Runs the built app in production mode.');
187+
console.log();
188+
console.log('We suggest that you begin by typing:');
189+
console.log();
190+
console.log(chalk.cyan(' cd'), cdpath);
191+
console.log(
192+
` ${chalk.cyan(`${packageManager} ${useYarn ? '' : 'run '}dev`)}`
193+
);
194+
console.log();
195+
}

helpers/get-pkg-manager.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { execSync } from 'child_process';
2+
3+
export type PackageManager = 'npm' | 'pnpm' | 'yarn';
4+
5+
export function getPkgManager(): PackageManager {
6+
try {
7+
const userAgent = process.env.npm_config_user_agent;
8+
if (userAgent) {
9+
if (userAgent.startsWith('yarn')) {
10+
return 'yarn';
11+
} else if (userAgent.startsWith('pnpm')) {
12+
return 'pnpm';
13+
}
14+
}
15+
try {
16+
execSync('yarn --version', { stdio: 'ignore' });
17+
return 'yarn';
18+
} catch {
19+
execSync('pnpm --version', { stdio: 'ignore' });
20+
return 'pnpm';
21+
}
22+
} catch {
23+
return 'npm';
24+
}
25+
}

helpers/git.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/* eslint-disable import/no-extraneous-dependencies */
2+
import { execSync } from 'child_process';
3+
import path from 'path';
4+
import rimraf from 'rimraf';
5+
6+
function isInGitRepository(): boolean {
7+
try {
8+
execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
9+
return true;
10+
} catch (_) {}
11+
return false;
12+
}
13+
14+
function isInMercurialRepository(): boolean {
15+
try {
16+
execSync('hg --cwd . root', { stdio: 'ignore' });
17+
return true;
18+
} catch (_) {}
19+
return false;
20+
}
21+
22+
export function tryGitInit(root: string): boolean {
23+
let didInit = false;
24+
try {
25+
execSync('git --version', { stdio: 'ignore' });
26+
if (isInGitRepository() || isInMercurialRepository()) {
27+
return false;
28+
}
29+
30+
execSync('git init', { stdio: 'ignore' });
31+
didInit = true;
32+
33+
execSync('git checkout -b main', { stdio: 'ignore' });
34+
35+
execSync('git add -A', { stdio: 'ignore' });
36+
execSync('git commit -m "Initial commit from Create Next App"', {
37+
stdio: 'ignore',
38+
});
39+
return true;
40+
} catch (e) {
41+
if (didInit) {
42+
try {
43+
rimraf.sync(path.join(root, '.git'));
44+
} catch (_) {}
45+
}
46+
return false;
47+
}
48+
}

helpers/install.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/* eslint-disable import/no-extraneous-dependencies */
2+
import chalk from 'chalk';
3+
import spawn from 'cross-spawn';
4+
import type { PackageManager } from './get-pkg-manager';
5+
6+
interface InstallArgs {
7+
/**
8+
* Indicate whether to install packages using npm, pnpm or Yarn.
9+
*/
10+
packageManager: PackageManager;
11+
/**
12+
* Indicate whether there is an active Internet connection.
13+
*/
14+
isOnline: boolean;
15+
/**
16+
* Indicate whether the given dependencies are devDependencies.
17+
*/
18+
devDependencies?: boolean;
19+
}
20+
21+
/**
22+
* Spawn a package manager installation with either Yarn or NPM.
23+
*
24+
* @returns A Promise that resolves once the installation is finished.
25+
*/
26+
export function install(
27+
root: string,
28+
dependencies: string[] | null,
29+
{ packageManager, isOnline, devDependencies }: InstallArgs
30+
): Promise<void> {
31+
/**
32+
* (p)npm-specific command-line flags.
33+
*/
34+
const npmFlags: string[] = [];
35+
/**
36+
* Yarn-specific command-line flags.
37+
*/
38+
const yarnFlags: string[] = [];
39+
/**
40+
* Return a Promise that resolves once the installation is finished.
41+
*/
42+
return new Promise((resolve, reject) => {
43+
let args: string[];
44+
let command = packageManager;
45+
const useYarn = packageManager === 'yarn';
46+
47+
if (dependencies && dependencies.length) {
48+
/**
49+
* If there are dependencies, run a variation of `{packageManager} add`.
50+
*/
51+
if (useYarn) {
52+
/**
53+
* Call `yarn add --exact (--offline)? (-D)? ...`.
54+
*/
55+
args = ['add', '--exact'];
56+
if (!isOnline) args.push('--offline');
57+
args.push('--cwd', root);
58+
if (devDependencies) args.push('--dev');
59+
args.push(...dependencies);
60+
} else {
61+
/**
62+
* Call `(p)npm install [--save|--save-dev] ...`.
63+
*/
64+
args = ['install', '--save-exact'];
65+
args.push(devDependencies ? '--save-dev' : '--save');
66+
args.push(...dependencies);
67+
}
68+
} else {
69+
/**
70+
* If there are no dependencies, run a variation of `{packageManager}
71+
* install`.
72+
*/
73+
args = ['install'];
74+
if (!isOnline) {
75+
console.log(chalk.yellow('You appear to be offline.'));
76+
if (useYarn) {
77+
console.log(chalk.yellow('Falling back to the local Yarn cache.'));
78+
console.log();
79+
args.push('--offline');
80+
} else {
81+
console.log();
82+
}
83+
}
84+
}
85+
/**
86+
* Add any package manager-specific flags.
87+
*/
88+
if (useYarn) {
89+
args.push(...yarnFlags);
90+
} else {
91+
args.push(...npmFlags);
92+
}
93+
/**
94+
* Spawn the installation process.
95+
*/
96+
const child = spawn(command, args, {
97+
stdio: 'inherit',
98+
env: { ...process.env, ADBLOCK: '1', DISABLE_OPENCOLLECTIVE: '1' },
99+
});
100+
child.on('close', (code) => {
101+
if (code !== 0) {
102+
reject({ command: `${command} ${args.join(' ')}` });
103+
return;
104+
}
105+
resolve();
106+
});
107+
});
108+
}

0 commit comments

Comments
 (0)