create-nextify 0.1.10 → 0.1.13

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.
@@ -0,0 +1,156 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import { createServer as createHttpServer } from 'node:http';
4
+ const PORT = Number(process.env.PORT ?? 3000);
5
+ const PROJECT_ROOT = process.env.NEXTIFY_ROOT ?? process.cwd();
6
+ const PAGES_DIR = path.join(PROJECT_ROOT, 'pages');
7
+ function toRoutePath(filePath) {
8
+ const clean = filePath
9
+ .replace(/\\/g, '/')
10
+ .replace(/^pages\//, '')
11
+ .replace(/\.(t|j)sx?$/, '')
12
+ .replace(/index$/, '');
13
+ if (!clean)
14
+ return '/';
15
+ return '/' + clean.split('/').filter(Boolean)
16
+ .map((seg) => seg.replace(/^\[(.+)\]$/, ':$1')).join('/');
17
+ }
18
+ function buildRouteManifest(files) {
19
+ return files.map((file) => {
20
+ const normalized = file.replace(/\\/g, '/');
21
+ const routePath = toRoutePath(normalized);
22
+ const base = path.basename(normalized);
23
+ const kind = normalized.includes('/api/') ? 'api'
24
+ : base.startsWith('middleware') ? 'middleware' : 'page';
25
+ return { file: normalized, routePath, kind };
26
+ });
27
+ }
28
+ function getPageFiles() {
29
+ if (!fs.existsSync(PAGES_DIR))
30
+ return [];
31
+ const files = [];
32
+ function walk(dir, base = '') {
33
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
34
+ const rel = base ? `${base}/${entry.name}` : entry.name;
35
+ if (entry.isDirectory())
36
+ walk(path.join(dir, entry.name), rel);
37
+ else if (/\.(tsx|ts|jsx|js)$/.test(entry.name))
38
+ files.push(`pages/${rel}`);
39
+ }
40
+ }
41
+ walk(PAGES_DIR);
42
+ return files;
43
+ }
44
+ function buildHtmlShell(routePath) {
45
+ const fileHint = routePath === '/' ? '/pages/index' : `/pages${routePath}`;
46
+ const candidates = JSON.stringify([
47
+ `${fileHint}.tsx`, `${fileHint}.jsx`, `${fileHint}.ts`, `${fileHint}.js`,
48
+ ]);
49
+ return `<!DOCTYPE html>
50
+ <html lang="pt-BR">
51
+ <head>
52
+ <meta charset="UTF-8" />
53
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
54
+ <title>Nextify.js</title>
55
+ </head>
56
+ <body>
57
+ <div id="root"></div>
58
+ <script type="module">
59
+ import { createElement } from 'react';
60
+ import { createRoot } from 'react-dom/client';
61
+ const candidates = ${candidates};
62
+ async function loadPage() {
63
+ let mod;
64
+ for (const c of candidates) { try { mod = await import(c); break; } catch {} }
65
+ const root = document.getElementById('root');
66
+ if (!mod?.default) {
67
+ root.innerHTML = '<div style="font-family:monospace;padding:2rem;color:#e53e3e"><h2>404 — Página não encontrada</h2></div>';
68
+ return;
69
+ }
70
+ createRoot(root).render(createElement(mod.default));
71
+ }
72
+ loadPage().catch((err) => {
73
+ document.getElementById('root').innerHTML = '<pre style="color:red;padding:2rem">' + err.stack + '</pre>';
74
+ });
75
+ </script>
76
+ </body>
77
+ </html>`;
78
+ }
79
+ export async function startDevServer(options = {}) {
80
+ const root = options.root ?? PROJECT_ROOT;
81
+ const port = options.port ?? PORT;
82
+ // Resolve vite e plugin-react a partir do projeto do usuário (node_modules local)
83
+ const vitePath = path.join(root, 'node_modules', 'vite', 'dist', 'node', 'index.js');
84
+ const reactPluginPath = path.join(root, 'node_modules', '@vitejs', 'plugin-react', 'dist', 'index.mjs');
85
+ let createViteServer, react;
86
+ try {
87
+ ({ createServer: createViteServer } = await import(vitePath));
88
+ ({ default: react } = await import(reactPluginPath));
89
+ }
90
+ catch (err) {
91
+ console.error('[nextify] Vite não encontrado. Rode: npm install vite @vitejs/plugin-react');
92
+ console.error(err.message);
93
+ process.exit(1);
94
+ }
95
+ const vite = await createViteServer({
96
+ root,
97
+ server: { middlewareMode: true },
98
+ appType: 'custom',
99
+ plugins: [react()],
100
+ resolve: { alias: { '@': path.join(root, 'src') } },
101
+ });
102
+ const manifest = buildRouteManifest(getPageFiles());
103
+ console.log('\n nextify dev\n');
104
+ for (const r of manifest)
105
+ console.log(` ${r.kind === 'api' ? '⚡' : '○'} ${r.routePath}`);
106
+ console.log('');
107
+ const server = createHttpServer(async (req, res) => {
108
+ const host = req.headers.host ?? `localhost:${port}`;
109
+ const pathname = new URL(`http://${host}${req.url}`).pathname;
110
+ if (pathname.startsWith('/@') || pathname.startsWith('/node_modules') ||
111
+ /\.(js|ts|tsx|jsx|css|svg|png|ico|woff2?|map)$/.test(pathname)) {
112
+ vite.middlewares(req, res, () => { res.statusCode = 404; res.end(); });
113
+ return;
114
+ }
115
+ const apiRoute = manifest.find((r) => r.kind === 'api' && r.routePath === pathname);
116
+ if (apiRoute) {
117
+ try {
118
+ const mod = await vite.ssrLoadModule(path.join(root, apiRoute.file));
119
+ if (typeof mod.default !== 'function')
120
+ throw new Error('API route sem default export');
121
+ const webReq = new globalThis.Request(`http://${host}${req.url}`, { method: req.method ?? 'GET' });
122
+ const webRes = await mod.default(webReq);
123
+ res.statusCode = webRes.status;
124
+ for (const [k, v] of webRes.headers.entries())
125
+ res.setHeader(k, v);
126
+ res.end(Buffer.from(await webRes.arrayBuffer()));
127
+ }
128
+ catch (err) {
129
+ vite.ssrFixStacktrace(err);
130
+ res.statusCode = 500;
131
+ res.setHeader('content-type', 'application/json');
132
+ res.end(JSON.stringify({ error: err.message }));
133
+ }
134
+ return;
135
+ }
136
+ try {
137
+ const html = await vite.transformIndexHtml(req.url ?? '/', buildHtmlShell(pathname));
138
+ const found = manifest.some((r) => r.kind === 'page' && r.routePath === pathname);
139
+ res.statusCode = found ? 200 : 404;
140
+ res.setHeader('content-type', 'text/html; charset=utf-8');
141
+ res.end(html);
142
+ }
143
+ catch (err) {
144
+ vite.ssrFixStacktrace(err);
145
+ res.statusCode = 500;
146
+ res.setHeader('content-type', 'text/html; charset=utf-8');
147
+ res.end(`<pre style="color:red;padding:2rem">${err.stack}</pre>`);
148
+ }
149
+ });
150
+ server.listen(port, () => {
151
+ console.log(` ➜ Local: \x1b[36mhttp://localhost:${port}\x1b[0m`);
152
+ console.log(` ➜ HMR: ativo\n`);
153
+ });
154
+ return { server, vite };
155
+ }
156
+ startDevServer();
package/dist/index.js CHANGED
@@ -12,43 +12,69 @@ function createProject(target = 'nextify-app') {
12
12
  writeFileSync(join(root, 'package.json'), JSON.stringify({
13
13
  name: target,
14
14
  private: true,
15
+ type: 'module',
15
16
  scripts: {
16
17
  dev: 'nextify dev',
17
18
  build: 'nextify build',
18
- start: 'nextify start'
19
+ start: 'nextify start',
20
+ },
21
+ dependencies: {
22
+ react: '^18.3.1',
23
+ 'react-dom': '^18.3.1',
19
24
  },
20
25
  devDependencies: {
21
- "create-nextify": "^0.1.0"
22
- }
26
+ 'create-nextify': 'latest',
27
+ vite: '^5.4.19',
28
+ '@vitejs/plugin-react': '^4.3.4',
29
+ '@types/react': '^18.3.1',
30
+ '@types/react-dom': '^18.3.1',
31
+ typescript: '^5.0.0',
32
+ },
23
33
  }, null, 2));
24
34
  writeFileSync(join(root, 'pages', 'index.tsx'), `export default function Home() {
25
35
  return (
26
- <main>
36
+ <main style={{ fontFamily: 'sans-serif', padding: '2rem' }}>
27
37
  <h1>Bem-vindo ao Nextify.js 🚀</h1>
38
+ <p>Edite <code>pages/index.tsx</code> para começar.</p>
28
39
  </main>
29
40
  );
30
41
  }
31
42
  `);
32
43
  writeFileSync(join(root, 'pages', 'api', 'health.ts'), `export default async function handler() {
33
44
  return new Response(JSON.stringify({ ok: true }), {
34
- headers: { 'content-type': 'application/json' }
45
+ headers: { 'content-type': 'application/json' },
35
46
  });
36
47
  }
37
48
  `);
49
+ writeFileSync(join(root, 'tsconfig.json'), JSON.stringify({
50
+ compilerOptions: {
51
+ target: 'ES2022',
52
+ module: 'ESNext',
53
+ moduleResolution: 'bundler',
54
+ jsx: 'react-jsx',
55
+ strict: true,
56
+ esModuleInterop: true,
57
+ skipLibCheck: true,
58
+ },
59
+ include: ['pages', 'src'],
60
+ }, null, 2));
38
61
  console.log(`\n✔ Projeto criado em: ${root}`);
39
62
  console.log('\nPróximos passos:\n');
40
63
  console.log(` cd ${target}`);
41
64
  console.log(' npm install');
42
65
  console.log(' npm run dev\n');
43
66
  }
44
- function runDevServer(port) {
45
- const server = http.createServer((_req, res) => {
46
- res.setHeader('content-type', 'text/plain; charset=utf-8');
47
- res.end('Nextify dev server ativo 🚀');
48
- });
49
- server.listen(port, () => {
50
- console.log(`Nextify dev server em http://localhost:${port}`);
51
- });
67
+ async function runDevServer(port) {
68
+ try {
69
+ // devServer.js está embutido no próprio pacote create-nextify (dist/devServer.js)
70
+ const devServerUrl = new URL('./devServer.js', import.meta.url).href;
71
+ const { startDevServer } = await import(devServerUrl);
72
+ await startDevServer({ root: process.cwd(), port });
73
+ }
74
+ catch (err) {
75
+ console.error('[nextify] Erro ao iniciar dev server:', err.message);
76
+ process.exit(1);
77
+ }
52
78
  }
53
79
  function runProdServer(port) {
54
80
  const server = http.createServer((_req, res) => {
@@ -56,16 +82,13 @@ function runProdServer(port) {
56
82
  res.end('Nextify production server ativo 🚀');
57
83
  });
58
84
  server.listen(port, () => {
59
- console.log(`Nextify start server em http://localhost:${port}`);
85
+ console.log(`Nextify start em http://localhost:${port}`);
60
86
  });
61
87
  }
62
88
  function runBuild() {
63
89
  mkdirSync(join(process.cwd(), 'dist'), { recursive: true });
64
- writeFileSync(join(process.cwd(), 'dist', 'route-manifest.json'), JSON.stringify({
65
- generatedAt: new Date().toISOString(),
66
- note: 'Manifesto de rotas gerado pelo CLI do Nextify.'
67
- }, null, 2));
68
- console.log('✔ Build do Nextify concluído. Artefatos em dist/');
90
+ writeFileSync(join(process.cwd(), 'dist', 'route-manifest.json'), JSON.stringify({ generatedAt: new Date().toISOString() }, null, 2));
91
+ console.log('✔ Build concluído. Artefatos em dist/');
69
92
  }
70
93
  function showHelp() {
71
94
  console.log(`
@@ -90,19 +113,17 @@ const command = args[0];
90
113
  const portArg = Number(process.env.PORT ?? args[1] ?? 3000);
91
114
  const port = Number.isFinite(portArg) ? portArg : 3000;
92
115
  switch (command) {
93
- case "create":
116
+ case 'create':
94
117
  createProject(args[1]);
95
118
  break;
96
- case "dev":
119
+ case 'dev':
97
120
  runDevServer(port);
98
121
  break;
99
- case "build":
122
+ case 'build':
100
123
  runBuild();
101
124
  break;
102
- case "start":
125
+ case 'start':
103
126
  runProdServer(port);
104
127
  break;
105
- default:
106
- // suporta: npx create-nextify minha-app
107
- createProject(command);
128
+ default: createProject(command);
108
129
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nextify",
3
- "version": "0.1.10",
3
+ "version": "0.1.13",
4
4
  "description": "CLI para criar aplicações Nextify.js",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,17 +11,23 @@
11
11
  "dist"
12
12
  ],
13
13
  "scripts": {
14
- "build": "node -e \"console.log('build de demonstração concluído')\"",
15
- "typecheck": "node -e \"console.log('typecheck básico: sem validação TS estrita neste pacote')\"",
14
+ "build": "tsc",
15
+ "typecheck": "tsc --noEmit",
16
16
  "test": "vitest run --config ./vitest.config.ts",
17
- "lint": "node -e \"console.log('sem lint configurado neste pacote')\""
17
+ "lint": "echo 'sem lint configurado neste pacote'"
18
+ },
19
+ "dependencies": {
20
+ "vite": "^5.4.19",
21
+ "@vitejs/plugin-react": "^4.3.4",
22
+ "react": "^18.3.1",
23
+ "react-dom": "^18.3.1"
18
24
  },
19
25
  "keywords": [
20
26
  "nextify",
21
27
  "framework",
22
28
  "cli"
23
29
  ],
24
- "author": "Ricardo Oliveira",
30
+ "author": "",
25
31
  "license": "MIT",
26
32
  "devDependencies": {
27
33
  "typescript": "^5.9.3"