mez-easyjs 0.1.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.
- package/LICENSE +21 -0
- package/README.md +40 -0
- package/bin/create-easy-app.js +60 -0
- package/bin/easy.js +7 -0
- package/compiler.d.ts +37 -0
- package/package.json +74 -0
- package/runtime.d.ts +103 -0
- package/src/commands/build.js +284 -0
- package/src/commands/create.js +476 -0
- package/src/commands/dev.js +434 -0
- package/src/commands/generate.js +104 -0
- package/src/commands/ws.js +157 -0
- package/src/compiler/codegen.js +1334 -0
- package/src/compiler/css.js +117 -0
- package/src/compiler/errors.js +65 -0
- package/src/compiler/expr.js +142 -0
- package/src/compiler/index.js +105 -0
- package/src/compiler/parse.js +115 -0
- package/src/compiler/strip-ts.js +39 -0
- package/src/compiler/template.js +419 -0
- package/src/index.js +128 -0
- package/src/runtime/app.js +115 -0
- package/src/runtime/component.js +222 -0
- package/src/runtime/css.js +35 -0
- package/src/runtime/errors.js +121 -0
- package/src/runtime/escape.js +62 -0
- package/src/runtime/events.js +151 -0
- package/src/runtime/hmr.js +157 -0
- package/src/runtime/index.js +40 -0
- package/src/runtime/overlay.js +95 -0
- package/src/runtime/reactive.js +172 -0
- package/src/runtime/router.js +423 -0
- package/src/runtime/scheduler.js +39 -0
- package/src/runtime/store.js +42 -0
- package/src/transform.js +106 -0
- package/templates/minimal/README.md +21 -0
- package/templates/minimal/easy.config.js +7 -0
- package/templates/minimal/index.html +13 -0
- package/templates/minimal/package.json +14 -0
- package/templates/minimal/src/App.easy +24 -0
- package/templates/minimal/src/main.js +4 -0
- package/templates/minimal/src/styles.css +14 -0
- package/templates/starter/README.md +18 -0
- package/templates/starter/easy.config.js +7 -0
- package/templates/starter/index.html +13 -0
- package/templates/starter/package.json +14 -0
- package/templates/starter/src/App.easy +69 -0
- package/templates/starter/src/components/Counter.easy +76 -0
- package/templates/starter/src/components/Greeting.easy +24 -0
- package/templates/starter/src/main.js +11 -0
- package/templates/starter/src/pages/About.easy +20 -0
- package/templates/starter/src/pages/Home.easy +69 -0
- package/templates/starter/src/styles.css +14 -0
- package/templates/tailwind/README.md +23 -0
- package/templates/tailwind/easy.config.js +7 -0
- package/templates/tailwind/index.html +19 -0
- package/templates/tailwind/package.json +14 -0
- package/templates/tailwind/src/App.easy +53 -0
- package/templates/tailwind/src/main.js +4 -0
- package/templates/tailwind/src/styles.css +4 -0
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import readline from 'node:readline';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const PKG_ROOT = path.resolve(__dirname, '../..');
|
|
9
|
+
const MONOREPO_ROOT = path.resolve(PKG_ROOT, '../..');
|
|
10
|
+
const TEMPLATES_ROOT = path.resolve(PKG_ROOT, 'templates');
|
|
11
|
+
|
|
12
|
+
const TEMPLATES = {
|
|
13
|
+
minimal: {
|
|
14
|
+
id: 'minimal',
|
|
15
|
+
label: 'Minimal / Empty',
|
|
16
|
+
description: 'Clean skeleton — easy.config.js, App.easy, least deps'
|
|
17
|
+
},
|
|
18
|
+
starter: {
|
|
19
|
+
id: 'starter',
|
|
20
|
+
label: 'Starter / Example',
|
|
21
|
+
description: 'Counter, routing, parent→child props — ready for npm run dev'
|
|
22
|
+
},
|
|
23
|
+
tailwind: {
|
|
24
|
+
id: 'tailwind',
|
|
25
|
+
label: 'Tailwind (CDN)',
|
|
26
|
+
description: 'Play CDN utilities in markup — no PostCSS / npm tailwindcss'
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const NPM_NAME_RE =
|
|
31
|
+
/^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
|
|
32
|
+
|
|
33
|
+
const c = {
|
|
34
|
+
reset: '\x1b[0m',
|
|
35
|
+
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
36
|
+
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
|
37
|
+
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
38
|
+
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
39
|
+
yellow: (s) => `\x1b[33m${s}\x1b[0m`,
|
|
40
|
+
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
41
|
+
magenta: (s) => `\x1b[35m${s}\x1b[0m`
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Scaffold a new Easy.js project (interactive wizard by default).
|
|
46
|
+
*
|
|
47
|
+
* Flags:
|
|
48
|
+
* --name <n> Project name (also positional arg)
|
|
49
|
+
* --template <id> minimal | starter | tailwind
|
|
50
|
+
* --lang <js|ts> Language (only js supported in MVP)
|
|
51
|
+
* --cwd <dir> Parent directory for the project
|
|
52
|
+
* --yes / -y Accept defaults; skip prompts when possible
|
|
53
|
+
* --skip-install Do not run npm install
|
|
54
|
+
* --skip-git Do not git init / commit
|
|
55
|
+
*/
|
|
56
|
+
export async function createProject(nameArg, flags = {}) {
|
|
57
|
+
const parentDir = path.resolve(flags.cwd || process.cwd());
|
|
58
|
+
const interactive = process.stdin.isTTY && !flags.yes && !flags.y;
|
|
59
|
+
|
|
60
|
+
console.log(`
|
|
61
|
+
${c.cyan(c.bold(' easy.js'))} ${c.dim('— create a new project')}
|
|
62
|
+
`);
|
|
63
|
+
|
|
64
|
+
let projectName =
|
|
65
|
+
flags.name ||
|
|
66
|
+
nameArg ||
|
|
67
|
+
(interactive ? await promptName() : null) ||
|
|
68
|
+
'easy-app';
|
|
69
|
+
|
|
70
|
+
projectName = String(projectName).trim();
|
|
71
|
+
const nameCheck = validateNpmName(projectName);
|
|
72
|
+
if (!nameCheck.ok) {
|
|
73
|
+
throw new Error(nameCheck.error);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let language =
|
|
77
|
+
normalizeLang(flags.lang || flags.language) ||
|
|
78
|
+
(interactive ? await promptLanguage() : 'js');
|
|
79
|
+
if (language === 'ts') {
|
|
80
|
+
console.log(
|
|
81
|
+
c.yellow(
|
|
82
|
+
' TypeScript scaffolding is not available yet — using JavaScript.\n'
|
|
83
|
+
)
|
|
84
|
+
);
|
|
85
|
+
language = 'js';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let templateId =
|
|
89
|
+
normalizeTemplate(flags.template || flags.t) ||
|
|
90
|
+
(interactive ? await promptTemplate() : 'starter');
|
|
91
|
+
|
|
92
|
+
if (!TEMPLATES[templateId]) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Unknown template "${templateId}". Use: minimal | starter | tailwind`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const target = path.resolve(parentDir, projectName);
|
|
99
|
+
// Path traversal protection — project must stay under the chosen parent.
|
|
100
|
+
const relToParent = path.relative(parentDir, target);
|
|
101
|
+
if (
|
|
102
|
+
!relToParent ||
|
|
103
|
+
relToParent.startsWith('..') ||
|
|
104
|
+
path.isAbsolute(relToParent)
|
|
105
|
+
) {
|
|
106
|
+
throw new Error('Invalid project path (path traversal blocked).');
|
|
107
|
+
}
|
|
108
|
+
if (fs.existsSync(target) && fs.readdirSync(target).length > 0) {
|
|
109
|
+
throw new Error(`Directory not empty: ${target}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (interactive) {
|
|
113
|
+
const ok = await confirm(
|
|
114
|
+
`Create ${c.bold(projectName)} (${TEMPLATES[templateId].label}, ${language}) in\n ${target}?`
|
|
115
|
+
);
|
|
116
|
+
if (!ok) {
|
|
117
|
+
console.log(c.dim(' Cancelled.'));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const templateDir = path.join(TEMPLATES_ROOT, templateId);
|
|
123
|
+
if (!fs.existsSync(templateDir)) {
|
|
124
|
+
throw new Error(`Template files missing: ${templateDir}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
fs.mkdirSync(target, { recursive: true });
|
|
128
|
+
const vars = {
|
|
129
|
+
project_name: projectName,
|
|
130
|
+
projectName,
|
|
131
|
+
PROJECT_NAME: projectName
|
|
132
|
+
};
|
|
133
|
+
copyDir(templateDir, target, vars);
|
|
134
|
+
|
|
135
|
+
writePackageJson(target, projectName, templateId);
|
|
136
|
+
writeGitignore(target);
|
|
137
|
+
|
|
138
|
+
if (!flags['skip-git'] && !flags.skipGit) {
|
|
139
|
+
initGit(target);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!flags['skip-install'] && !flags.skipInstall) {
|
|
143
|
+
console.log(c.dim('\n Installing dependencies…\n'));
|
|
144
|
+
const install = runNpmInstall(target);
|
|
145
|
+
if (!install.ok) {
|
|
146
|
+
console.log(
|
|
147
|
+
c.yellow(
|
|
148
|
+
` npm install failed (you can run it manually in ${projectName}).\n ${install.error || ''}`
|
|
149
|
+
)
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!flags['skip-git'] && !flags.skipGit) {
|
|
155
|
+
initialCommit(target, projectName, templateId);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
printSuccess({ projectName, templateId, language, target });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function validateNpmName(name) {
|
|
162
|
+
if (!name || !String(name).trim()) {
|
|
163
|
+
return { ok: false, error: 'Project name cannot be empty.' };
|
|
164
|
+
}
|
|
165
|
+
const n = String(name).trim();
|
|
166
|
+
if (n === '.' || n === '..' || n.includes('..')) {
|
|
167
|
+
return { ok: false, error: 'Invalid project name.' };
|
|
168
|
+
}
|
|
169
|
+
if (/[\\]/.test(n) || /[\0]/.test(n)) {
|
|
170
|
+
return { ok: false, error: 'Project name cannot contain path separators.' };
|
|
171
|
+
}
|
|
172
|
+
// Allow a single / only for scoped packages (@scope/name).
|
|
173
|
+
const slashCount = (n.match(/\//g) || []).length;
|
|
174
|
+
if (slashCount > (n.startsWith('@') ? 1 : 0)) {
|
|
175
|
+
return { ok: false, error: 'Invalid project name (too many path segments).' };
|
|
176
|
+
}
|
|
177
|
+
if (/\s/.test(n)) {
|
|
178
|
+
return { ok: false, error: 'Project name cannot contain spaces.' };
|
|
179
|
+
}
|
|
180
|
+
if (/[A-Z]/.test(n)) {
|
|
181
|
+
return {
|
|
182
|
+
ok: false,
|
|
183
|
+
error: 'Project name must be lowercase (npm package rules).'
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
if (!NPM_NAME_RE.test(n)) {
|
|
187
|
+
return {
|
|
188
|
+
ok: false,
|
|
189
|
+
error:
|
|
190
|
+
'Invalid npm package name. Use lowercase letters, digits, hyphens, or @scope/name.'
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
if (n.startsWith('.') || n.startsWith('_')) {
|
|
194
|
+
return { ok: false, error: 'Project name cannot start with . or _.' };
|
|
195
|
+
}
|
|
196
|
+
return { ok: true };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function normalizeTemplate(raw) {
|
|
200
|
+
if (!raw || raw === true) return null;
|
|
201
|
+
const id = String(raw).toLowerCase().trim();
|
|
202
|
+
if (id === 'empty') return 'minimal';
|
|
203
|
+
if (id === 'example' || id === 'demo') return 'starter';
|
|
204
|
+
if (id === 'tw') return 'tailwind';
|
|
205
|
+
return TEMPLATES[id] ? id : id;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function normalizeLang(raw) {
|
|
209
|
+
if (!raw || raw === true) return null;
|
|
210
|
+
const v = String(raw).toLowerCase().trim();
|
|
211
|
+
if (v === 'javascript' || v === 'js') return 'js';
|
|
212
|
+
if (v === 'typescript' || v === 'ts') return 'ts';
|
|
213
|
+
return v;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function promptName() {
|
|
217
|
+
const answer = await ask(` ${c.bold('Project name:')} `, 'easy-app');
|
|
218
|
+
const check = validateNpmName(answer);
|
|
219
|
+
if (!check.ok) {
|
|
220
|
+
console.log(c.red(` ✖ ${check.error}`));
|
|
221
|
+
return promptName();
|
|
222
|
+
}
|
|
223
|
+
return answer.trim();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function promptLanguage() {
|
|
227
|
+
console.log(`
|
|
228
|
+
${c.bold('Language')}
|
|
229
|
+
${c.cyan('1)')} JavaScript ${c.green('(available)')}
|
|
230
|
+
${c.dim('2) TypeScript — coming soon (not available yet)')}
|
|
231
|
+
`);
|
|
232
|
+
const answer = await ask(' Choose [1]: ', '1');
|
|
233
|
+
if (answer.trim() === '2' || /^ts/i.test(answer)) {
|
|
234
|
+
return 'ts';
|
|
235
|
+
}
|
|
236
|
+
return 'js';
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function promptTemplate() {
|
|
240
|
+
const ids = Object.keys(TEMPLATES);
|
|
241
|
+
console.log(`\n ${c.bold('Template')}`);
|
|
242
|
+
ids.forEach((id, i) => {
|
|
243
|
+
const t = TEMPLATES[id];
|
|
244
|
+
console.log(
|
|
245
|
+
` ${c.cyan(`${i + 1})`)} ${t.label} ${c.dim(`— ${t.description}`)}`
|
|
246
|
+
);
|
|
247
|
+
});
|
|
248
|
+
console.log();
|
|
249
|
+
const answer = await ask(' Choose [2]: ', '2');
|
|
250
|
+
const n = Number(answer.trim());
|
|
251
|
+
if (n >= 1 && n <= ids.length) return ids[n - 1];
|
|
252
|
+
const byId = normalizeTemplate(answer);
|
|
253
|
+
if (TEMPLATES[byId]) return byId;
|
|
254
|
+
console.log(c.yellow(' Using starter.'));
|
|
255
|
+
return 'starter';
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function confirm(message) {
|
|
259
|
+
console.log(`\n ${message}`);
|
|
260
|
+
const answer = await ask(` ${c.bold('Continue?')} [Y/n] `, 'y');
|
|
261
|
+
return !/^(n|no)$/i.test(answer.trim());
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function ask(question, defaultValue = '') {
|
|
265
|
+
const rl = readline.createInterface({
|
|
266
|
+
input: process.stdin,
|
|
267
|
+
output: process.stdout
|
|
268
|
+
});
|
|
269
|
+
return new Promise((resolve) => {
|
|
270
|
+
rl.question(question, (answer) => {
|
|
271
|
+
rl.close();
|
|
272
|
+
const v = answer.trim();
|
|
273
|
+
resolve(v || defaultValue);
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function copyDir(src, dest, vars) {
|
|
279
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
280
|
+
for (const name of fs.readdirSync(src)) {
|
|
281
|
+
if (name === '.git' || name === 'node_modules') continue;
|
|
282
|
+
const from = path.join(src, name);
|
|
283
|
+
const to = path.join(dest, name);
|
|
284
|
+
const st = fs.statSync(from);
|
|
285
|
+
if (st.isDirectory()) {
|
|
286
|
+
copyDir(from, to, vars);
|
|
287
|
+
} else {
|
|
288
|
+
let content = fs.readFileSync(from);
|
|
289
|
+
const textLike = /\.(html?|js|mjs|cjs|json|css|md|easy|ejs|txt|svg)$/i.test(
|
|
290
|
+
name
|
|
291
|
+
);
|
|
292
|
+
if (textLike) {
|
|
293
|
+
let text = content.toString('utf8');
|
|
294
|
+
text = applyVars(text, vars);
|
|
295
|
+
fs.writeFileSync(to, text);
|
|
296
|
+
} else {
|
|
297
|
+
fs.writeFileSync(to, content);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function applyVars(text, vars) {
|
|
304
|
+
let out = text;
|
|
305
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
306
|
+
out = out.split(`{{${key}}}`).join(value);
|
|
307
|
+
}
|
|
308
|
+
return out;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function writePackageJson(target, projectName, templateId) {
|
|
312
|
+
const pkgPath = path.join(target, 'package.json');
|
|
313
|
+
let pkg;
|
|
314
|
+
if (fs.existsSync(pkgPath)) {
|
|
315
|
+
pkg = JSON.parse(applyVars(fs.readFileSync(pkgPath, 'utf8'), { projectName, project_name: projectName }));
|
|
316
|
+
} else {
|
|
317
|
+
pkg = {
|
|
318
|
+
name: projectName,
|
|
319
|
+
version: '0.1.0',
|
|
320
|
+
private: true,
|
|
321
|
+
type: 'module',
|
|
322
|
+
scripts: {
|
|
323
|
+
dev: 'easy dev',
|
|
324
|
+
build: 'easy build'
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
pkg.name = projectName;
|
|
330
|
+
// Prefer file: links into this monorepo when available; else version pins.
|
|
331
|
+
pkg.dependencies = resolveEasyDependencies(target);
|
|
332
|
+
|
|
333
|
+
if (!pkg.scripts) pkg.scripts = {};
|
|
334
|
+
if (!pkg.scripts.dev) pkg.scripts.dev = 'easy dev';
|
|
335
|
+
if (!pkg.scripts.build) pkg.scripts.build = 'easy build';
|
|
336
|
+
|
|
337
|
+
pkg.easyTemplate = templateId;
|
|
338
|
+
|
|
339
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Prefer file: links into this monorepo when present (local development).
|
|
344
|
+
* Fall back to published version numbers for consumers of published packages.
|
|
345
|
+
*/
|
|
346
|
+
/** Semver range used when scaffolding outside this monorepo (published path). */
|
|
347
|
+
const PUBLISHED_RANGE = '^0.1.0';
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Prefer file: links into this monorepo when present (local development).
|
|
351
|
+
* Fall back to published semver ranges for consumers of published packages.
|
|
352
|
+
*/
|
|
353
|
+
function resolveEasyDependencies(targetDir) {
|
|
354
|
+
const local = path.join(MONOREPO_ROOT, 'packages', 'easyjs');
|
|
355
|
+
const pkgJson = path.join(local, 'package.json');
|
|
356
|
+
if (fs.existsSync(pkgJson)) {
|
|
357
|
+
let rel = path.relative(targetDir, local);
|
|
358
|
+
rel = rel.split(path.sep).join('/');
|
|
359
|
+
if (!rel.startsWith('.')) rel = `./${rel}`;
|
|
360
|
+
return { 'mez-easyjs': `file:${rel}` };
|
|
361
|
+
}
|
|
362
|
+
return { 'mez-easyjs': PUBLISHED_RANGE };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function writeGitignore(target) {
|
|
366
|
+
const dest = path.join(target, '.gitignore');
|
|
367
|
+
if (fs.existsSync(dest)) return;
|
|
368
|
+
fs.writeFileSync(
|
|
369
|
+
dest,
|
|
370
|
+
`# easy.js
|
|
371
|
+
node_modules/
|
|
372
|
+
dist/
|
|
373
|
+
.easy/
|
|
374
|
+
.cache/
|
|
375
|
+
coverage/
|
|
376
|
+
*.log
|
|
377
|
+
.DS_Store
|
|
378
|
+
Thumbs.db
|
|
379
|
+
*.local
|
|
380
|
+
.env
|
|
381
|
+
.env.*
|
|
382
|
+
`
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function hasGit() {
|
|
387
|
+
const r = spawnSync('git', ['--version'], { encoding: 'utf8' });
|
|
388
|
+
return r.status === 0;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function initGit(target) {
|
|
392
|
+
if (!hasGit()) {
|
|
393
|
+
console.log(c.dim(' git not available — skipping git init'));
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
const r = spawnSync('git', ['init'], { cwd: target, encoding: 'utf8' });
|
|
397
|
+
if (r.status !== 0) {
|
|
398
|
+
console.log(c.dim(' git init failed — skipping'));
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function initialCommit(target, projectName, templateId) {
|
|
403
|
+
if (!hasGit()) return;
|
|
404
|
+
if (!fs.existsSync(path.join(target, '.git'))) return;
|
|
405
|
+
|
|
406
|
+
spawnSync('git', ['add', '-A'], { cwd: target, encoding: 'utf8' });
|
|
407
|
+
const msg = `chore: initial commit from easy create (${templateId})\n\nScaffolded ${projectName} with the ${templateId} template.`;
|
|
408
|
+
const r = spawnSync('git', ['commit', '-m', msg], {
|
|
409
|
+
cwd: target,
|
|
410
|
+
encoding: 'utf8',
|
|
411
|
+
env: {
|
|
412
|
+
...process.env,
|
|
413
|
+
GIT_AUTHOR_NAME: process.env.GIT_AUTHOR_NAME || 'easy create',
|
|
414
|
+
GIT_AUTHOR_EMAIL: process.env.GIT_AUTHOR_EMAIL || 'easy@localhost',
|
|
415
|
+
GIT_COMMITTER_NAME: process.env.GIT_COMMITTER_NAME || 'easy create',
|
|
416
|
+
GIT_COMMITTER_EMAIL: process.env.GIT_COMMITTER_EMAIL || 'easy@localhost'
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
if (r.status !== 0) {
|
|
420
|
+
console.log(
|
|
421
|
+
c.dim(
|
|
422
|
+
' git commit skipped (configure user.name / user.email, or commit manually)'
|
|
423
|
+
)
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function runNpmInstall(target) {
|
|
429
|
+
const isWin = process.platform === 'win32';
|
|
430
|
+
const npmCmd = isWin ? 'npm.cmd' : 'npm';
|
|
431
|
+
const r = spawnSync(npmCmd, ['install'], {
|
|
432
|
+
cwd: target,
|
|
433
|
+
encoding: 'utf8',
|
|
434
|
+
stdio: 'inherit',
|
|
435
|
+
// Avoid shell:true (DEP0190); npm.cmd is invokable directly on Windows.
|
|
436
|
+
windowsHide: true
|
|
437
|
+
});
|
|
438
|
+
return {
|
|
439
|
+
ok: r.status === 0,
|
|
440
|
+
error: r.status === 0 ? null : `exit ${r.status}`
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function printSuccess({ projectName, templateId, language, target }) {
|
|
445
|
+
const art = `
|
|
446
|
+
${c.cyan(' ___ __ _ ___ _ _')}
|
|
447
|
+
${c.cyan(' / _ \\/ _` / __| | | |')}
|
|
448
|
+
${c.cyan(' | __/ (_| \\__ \\ |_| |')}
|
|
449
|
+
${c.cyan(' \\___|\\__,_|___/\\__, |')}
|
|
450
|
+
${c.cyan(' |___/ ')}${c.bold('.js')}
|
|
451
|
+
`;
|
|
452
|
+
|
|
453
|
+
console.log(art);
|
|
454
|
+
console.log(c.green(c.bold(' ✔ Project ready!\n')));
|
|
455
|
+
console.log(` ${c.bold('Name:')} ${projectName}`);
|
|
456
|
+
console.log(` ${c.bold('Template:')} ${TEMPLATES[templateId].label} (${templateId})`);
|
|
457
|
+
console.log(` ${c.bold('Language:')} ${language === 'js' ? 'JavaScript' : language}`);
|
|
458
|
+
console.log(` ${c.bold('Path:')} ${target}`);
|
|
459
|
+
if (templateId === 'tailwind') {
|
|
460
|
+
console.log(
|
|
461
|
+
`\n ${c.dim('Tailwind via Play CDN (@tailwindcss/browser) — no PostCSS.')}`
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
console.log(`
|
|
465
|
+
${c.bold('Next steps:')}
|
|
466
|
+
|
|
467
|
+
${c.cyan(`cd ${projectName}`)}
|
|
468
|
+
${c.cyan('npm run dev')}
|
|
469
|
+
|
|
470
|
+
${c.dim('Docs: https://github.com/easyjs/easy-js')}
|
|
471
|
+
${c.dim('Issues: https://github.com/easyjs/easy-js/issues')}
|
|
472
|
+
`);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Re-export registry for tests / help
|
|
476
|
+
export { TEMPLATES, TEMPLATES_ROOT, PKG_ROOT };
|