Skip to content

Commit 9fdca9e

Browse files
committed
Add a CLI to create new Parcel apps
1 parent e3a2b72 commit 9fdca9e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+959
-18
lines changed

.eslintignore

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ packages/*/*/test/mochareporters.json
1111
packages/core/integration-tests/test/input/**
1212
packages/core/utils/test/input/**
1313
packages/utils/create-react-app/templates
14+
packages/utils/create-parcel-app/templates
1415
packages/examples
1516

1617
# Generated by the build

gulpfile.js

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const paths = {
3232
packageJson: [
3333
'packages/core/parcel/package.json',
3434
'packages/utils/create-react-app/package.json',
35+
'packages/utils/create-parcel/package.json',
3536
'packages/dev/query/package.json',
3637
'packages/dev/bundle-stats-cli/package.json',
3738
],

packages/packagers/react-static/src/ReactStaticPackager.js

-1
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,6 @@ async function loadBundleUncached(
264264
];
265265
});
266266
} else if (entryBundle) {
267-
// console.log('here', entryBundle)
268267
queue.add(async () => {
269268
let {assets: subAssets} = await loadBundle(
270269
entryBundle,
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "create-parcel",
3+
"version": "2.13.3",
4+
"bin": {
5+
"create-parcel": "lib/create-parcel.js"
6+
},
7+
"main": "src/create-parcel.js",
8+
"repository": {
9+
"type": "git",
10+
"url": "https://github.com/parcel-bundler/parcel.git",
11+
"directory": "packages/utils/create-parcel"
12+
},
13+
"source": "src/create-parcel.js",
14+
"files": [
15+
"templates",
16+
"lib"
17+
],
18+
"license": "MIT",
19+
"publishConfig": {
20+
"access": "public"
21+
},
22+
"dependencies": {}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
#!/usr/bin/env node
2+
3+
// @flow
4+
/* eslint-disable no-console */
5+
6+
// $FlowFixMe
7+
import fs from 'fs/promises';
8+
import {readdirSync} from 'fs';
9+
import path from 'path';
10+
import {spawn as _spawn} from 'child_process';
11+
// $FlowFixMe
12+
import {parseArgs, styleText} from 'util';
13+
14+
const supportsEmoji = isUnicodeSupported();
15+
16+
// Fallback symbols for Windows from https://en.wikipedia.org/wiki/Code_page_437
17+
const success: string = supportsEmoji ? '✨' : '√';
18+
const error: string = supportsEmoji ? '🚨' : '×';
19+
20+
const {positionals} = parseArgs({
21+
allowPositionals: true,
22+
options: {},
23+
});
24+
25+
let template = positionals[0];
26+
if (!template) {
27+
let packageManager = getCurrentPackageManager()?.name;
28+
console.error(
29+
`Usage: ${packageManager ?? 'npm'} create <template> [directory]\n`,
30+
);
31+
printAvailableTemplates();
32+
console.log('');
33+
process.exit(1);
34+
}
35+
36+
let name = positionals[1];
37+
if (!name) {
38+
name = '.';
39+
}
40+
41+
install(template, name).then(
42+
() => {
43+
process.exit(0);
44+
},
45+
err => {
46+
console.error(err);
47+
process.exit(1);
48+
},
49+
);
50+
51+
async function install(template: string, name: string) {
52+
let templateDir = path.join(__dirname, '..', 'templates', template);
53+
try {
54+
await fs.stat(templateDir);
55+
} catch {
56+
console.error(
57+
style(['red', 'bold'], `${error} Unknown template ${template}.\n`),
58+
);
59+
printAvailableTemplates();
60+
console.log('');
61+
process.exit(1);
62+
return;
63+
}
64+
65+
if (name === '.') {
66+
if ((await fs.readdir(name)).length !== 0) {
67+
console.error(style(['red', 'bold'], `${error} Directory is not empty.`));
68+
process.exit(1);
69+
return;
70+
}
71+
} else {
72+
try {
73+
await fs.stat(name);
74+
console.error(style(['red', 'bold'], `${error} ${name} already exists.`));
75+
process.exit(1);
76+
return;
77+
} catch {
78+
// ignore
79+
}
80+
await fs.mkdir(name, {recursive: true});
81+
}
82+
83+
await spawn('git', ['init'], {
84+
stdio: 'inherit',
85+
cwd: name,
86+
});
87+
88+
await fs.cp(templateDir, name, {
89+
recursive: true,
90+
});
91+
92+
let packageManager = getCurrentPackageManager()?.name;
93+
switch (packageManager) {
94+
case 'yarn':
95+
await spawn('yarn', [], {cwd: name, stdio: 'inherit'});
96+
break;
97+
case 'pnpm':
98+
await spawn('pnpm', ['install'], {cwd: name, stdio: 'inherit'});
99+
break;
100+
case 'npm':
101+
default:
102+
await spawn(
103+
'npm',
104+
['install', '--legacy-peer-deps', '--no-audit', '--no-fund'],
105+
{cwd: name, stdio: 'inherit'},
106+
);
107+
break;
108+
}
109+
110+
await spawn('git', ['add', '-A'], {cwd: name});
111+
await spawn(
112+
'git',
113+
['commit', '--quiet', '-a', '-m', 'Initial commit from create-parcel'],
114+
{
115+
stdio: 'inherit',
116+
cwd: name,
117+
},
118+
);
119+
120+
console.log('');
121+
console.log(style(['green', 'bold'], `${success} Your new app is ready!\n`));
122+
console.log('To get started, run the following commands:');
123+
console.log('');
124+
if (name !== '.') {
125+
console.log(` cd ${name}`);
126+
}
127+
console.log(` ${packageManager ?? 'npm'} start`);
128+
console.log('');
129+
}
130+
131+
function spawn(cmd, args, opts) {
132+
return new Promise((resolve, reject) => {
133+
let p = _spawn(cmd, args, opts);
134+
p.on('close', (code, signal) => {
135+
if (code || signal) {
136+
reject(new Error(`${cmd} failed with exit code ${code}`));
137+
} else {
138+
resolve();
139+
}
140+
});
141+
});
142+
}
143+
144+
function getCurrentPackageManager(
145+
userAgent: ?string = process.env.npm_config_user_agent,
146+
): ?{|name: string, version: string|} {
147+
if (!userAgent) {
148+
return undefined;
149+
}
150+
151+
const pmSpec = userAgent.split(' ')[0];
152+
const separatorPos = pmSpec.lastIndexOf('/');
153+
const name = pmSpec.substring(0, separatorPos);
154+
return {
155+
name: name,
156+
version: pmSpec.substring(separatorPos + 1),
157+
};
158+
}
159+
160+
function printAvailableTemplates() {
161+
console.error('Available templates:\n');
162+
for (let dir of readdirSync(path.join(__dirname, '..', 'templates'))) {
163+
console.error(` • ${dir}`);
164+
}
165+
}
166+
167+
// From https://github.com/sindresorhus/is-unicode-supported/blob/8f123916d5c25a87c4f966dcc248b7ca5df2b4ca/index.js
168+
// This package is ESM-only so it has to be vendored
169+
function isUnicodeSupported() {
170+
if (process.platform !== 'win32') {
171+
return process.env.TERM !== 'linux'; // Linux console (kernel)
172+
}
173+
174+
return (
175+
Boolean(process.env.CI) ||
176+
Boolean(process.env.WT_SESSION) || // Windows Terminal
177+
process.env.ConEmuTask === '{cmd::Cmder}' || // ConEmu and cmder
178+
process.env.TERM_PROGRAM === 'vscode' ||
179+
process.env.TERM === 'xterm-256color' ||
180+
process.env.TERM === 'alacritty'
181+
);
182+
}
183+
184+
function style(format, text) {
185+
if (styleText) {
186+
return styleText(format, text);
187+
} else {
188+
return text;
189+
}
190+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.parcel-cache/
2+
dist/
3+
node_modules/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "parcel-react-client-starter",
3+
"private": true,
4+
"version": "0.0.0",
5+
"source": "src/index.html",
6+
"scripts": {
7+
"start": "parcel",
8+
"build": "parcel build"
9+
},
10+
"dependencies": {
11+
"react": "^19.0.0",
12+
"react-dom": "^19.0.0"
13+
},
14+
"devDependencies": {
15+
"@types/react": "^19.0.0",
16+
"@types/react-dom": "^19.0.0",
17+
"parcel": "^2.13.3"
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
html {
2+
color-scheme: light dark;
3+
font-family: system-ui;
4+
display: flex;
5+
align-items: center;
6+
justify-content: center;
7+
height: 100%;
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import './App.css';
2+
3+
export function App() {
4+
return (
5+
<>
6+
<h1>Parcel React App</h1>
7+
<p>Edit <code>src/App.tsx</code> to get started!</p>
8+
</>
9+
);
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8"/>
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Parcel React App</title>
7+
</head>
8+
<body>
9+
<div id="app"></div>
10+
<script type="module" src="index.tsx"></script>
11+
</body>
12+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { createRoot } from 'react-dom/client';
2+
import { StrictMode } from 'react';
3+
import { App } from './App';
4+
5+
let container = document.getElementById("app")!;
6+
let root = createRoot(container)
7+
root.render(
8+
<StrictMode>
9+
<App />
10+
</StrictMode>
11+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"compilerOptions": {
3+
/* Visit https://aka.ms/tsconfig to read more about this file */
4+
"target": "ES2020",
5+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
6+
"jsx": "react-jsx",
7+
"useDefineForClassFields": true,
8+
9+
/* Modules */
10+
"module": "ESNext",
11+
"moduleResolution": "bundler",
12+
13+
/* Emit */
14+
"noEmit": true,
15+
16+
/* Interop Constraints */
17+
"isolatedModules": true,
18+
"allowSyntheticDefaultImports": true,
19+
"allowImportingTsExtensions": true,
20+
"esModuleInterop": true,
21+
"forceConsistentCasingInFileNames": true,
22+
23+
/* Type Checking */
24+
"strict": true,
25+
26+
/* Completeness */
27+
"skipLibCheck": true
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.parcel-cache/
2+
dist/
3+
node_modules/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "parcel-react-server-starter",
3+
"private": true,
4+
"version": "0.0.0",
5+
"server": "dist/server.js",
6+
"source": "src/server.tsx",
7+
"targets": {
8+
"server": {
9+
"context": "react-server",
10+
"includeNodeModules": {
11+
"express": false
12+
}
13+
}
14+
},
15+
"scripts": {
16+
"start": "parcel",
17+
"build": "parcel build"
18+
},
19+
"dependencies": {
20+
"express": "^4.21.2",
21+
"react": "canary",
22+
"react-dom": "canary",
23+
"react-server-dom-parcel": "canary",
24+
"rsc-html-stream": "^0.0.4"
25+
},
26+
"devDependencies": {
27+
"@types/react": "^19.0.0",
28+
"@types/react-dom": "^19.0.0",
29+
"parcel": "^2.13.3"
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
5+
export function Counter() {
6+
let [count, setCount] = useState(0);
7+
8+
return (
9+
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
10+
);
11+
}

0 commit comments

Comments
 (0)