foobara-empty-typescript-react-project-generator 0.1.0 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d88f0fadc7f0afc451cff43e45176a18e7cf1a9b3f7bf731a6123ff7f702fb1b
4
- data.tar.gz: efe8102579f2d61c603365f8c874523eff5cfa1bab1e554763a7a466b5fe2341
3
+ metadata.gz: 7f7b4ac21e56483eddd6d15620cf1edaaccd9448e227d17dd2fc73f33c1d38d6
4
+ data.tar.gz: 7b6a39dbbf4e8e24e739af7cc782677f5dc10139d4971d40b473935c5763a030
5
5
  SHA512:
6
- metadata.gz: e7efd4a034de02009d2c62d493550b155b91e690ce80e70ec1f9321b3fb773eceb1ed5662a93584952daf17af131cc007f4ff3cd3219d5f45ea6bf165d7e8a81
7
- data.tar.gz: 198dfe37e4f82b40e8bd0040728129a581f1608399f67b606d3fc6eb9bdc33677299ef663cf1447e698d2d33cb627fb94e814236b97805ac169442d1afd52948
6
+ metadata.gz: 324c237a91f5351a2b885d541b364d6593c5341960cdcf021851926f841ffdb21815bee05dc1b46926808eed81c400a21d5e397e8ae919462e8af9d889306151
7
+ data.tar.gz: cae37cc52abad3774ec99bfbc02c2719cee244d900f6a2efe9384177011302837cf27a6a6d5bcae07964d3ff7145b73c2cd0d008745f31bb8ab262d0e4488821
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## [0.3.0] - 2026-07-05
2
+
3
+ - Wire up react router/nav
4
+ - Wire up a sitemap/prerender stuff
5
+ - Switch to oxlint in generated projects
6
+
7
+ ## [0.2.0] - 2026-03-19
8
+
9
+ - Switch to generating the project with Vite instead of CRA
10
+
1
11
  ## [0.1.0] - 2025-08-22
2
12
 
3
13
  - Mark as compatible with Foobara 0.1.0
@@ -8,6 +8,7 @@ module Foobara
8
8
  project_dir :string, :required
9
9
  github_organization :string
10
10
  push_to_github :boolean, default: false
11
+ site_url :string, default: "https://example.com"
11
12
  end
12
13
  end
13
14
  end
@@ -7,8 +7,10 @@ module Foobara
7
7
  def manifest_to_generator_classes(manifest)
8
8
  case manifest
9
9
  when EmptyTypescriptReactProjectConfig
10
- # Nothing to do yet re: rendering templates. Everything is untemplated so far.
11
- []
10
+ [
11
+ Generators::RobotsTxtGenerator,
12
+ Generators::RoutesTsGenerator
13
+ ]
12
14
  else
13
15
  # :nocov:
14
16
  raise "Not sure how build a generator for a #{manifest}"
@@ -16,6 +18,10 @@ module Foobara
16
18
  end
17
19
  end
18
20
  end
21
+
22
+ alias empty_typescript_react_project_config relevant_manifest
23
+
24
+ def templates_dir = "#{__dir__}/../templates"
19
25
  end
20
26
  end
21
27
  end
@@ -18,10 +18,7 @@ module Foobara
18
18
  add_initial_elements_to_generate
19
19
 
20
20
  each_element_to_generate do
21
- # currently there are no templated files to write and so this code path isn't hit
22
- # :nocov:
23
21
  generate_element
24
- # :nocov:
25
22
  end
26
23
 
27
24
  paths_to_source_code
@@ -0,0 +1,13 @@
1
+ require_relative "../empty_typescript_react_project_generator"
2
+
3
+ module Foobara
4
+ module Generators
5
+ module EmptyTypescriptReactProjectGenerator
6
+ module Generators
7
+ class RobotsTxtGenerator < EmptyTypescriptReactProjectGenerator
8
+ def template_path = ["public", "robots.txt.erb"]
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ require_relative "../empty_typescript_react_project_generator"
2
+
3
+ module Foobara
4
+ module Generators
5
+ module EmptyTypescriptReactProjectGenerator
6
+ module Generators
7
+ class RoutesTsGenerator < EmptyTypescriptReactProjectGenerator
8
+ def template_path = ["scripts", "routes.ts.erb"]
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -54,19 +54,17 @@ module Foobara
54
54
  end
55
55
 
56
56
  def run_pre_generation_tasks
57
- run_npx_create_react_app
58
- add_necessary_dev_dependencies_for_eslint
59
- fix_uncorrectable_lint_violations
60
- eslint_fix
57
+ run_npm_create_vite
58
+ npm_install
61
59
  git_init
62
60
  git_add_all
63
- git_commit_lint_fixes
61
+ git_commit_initial_setup
64
62
  end
65
63
 
66
- def run_npx_create_react_app
67
- puts "created empty project with create-react-app..."
64
+ def run_npm_create_vite
65
+ puts "creating empty project with Vite..."
68
66
 
69
- cmd = "npx create-react-app --template typescript #{empty_typescript_react_project_config.project_dir}"
67
+ cmd = "npm create vite@latest #{empty_typescript_react_project_config.project_dir} -- --template react-ts"
70
68
 
71
69
  FileUtils.mkdir_p output_parent_directory
72
70
 
@@ -75,16 +73,10 @@ module Foobara
75
73
  end
76
74
  end
77
75
 
78
- def add_necessary_dev_dependencies_for_eslint
79
- puts "adding dependencies needed for linter to actually work..."
76
+ def npm_install
77
+ puts "installing dependencies..."
80
78
 
81
- cmd = "npm install --save-dev " \
82
- "@babel/plugin-proposal-private-property-in-object@^7.21.11 " \
83
- "@eslint/create-config@^0.4.6 " \
84
- "eslint-config-standard-with-typescript@^37.0.0 " \
85
- "eslint-plugin-n@^16.5.0 " \
86
- "eslint-plugin-promise@^6.1.1 " \
87
- "typescript@^4.0.0 "
79
+ cmd = "npm install"
88
80
 
89
81
  Dir.chdir project_directory do
90
82
  run_cmd_and_write_output(cmd)
@@ -92,7 +84,11 @@ module Foobara
92
84
  end
93
85
 
94
86
  def run_post_generation_tasks
95
- eslint_fix
87
+ npm_install_react_router
88
+ npm_install_sitemap_and_prerender_dependencies
89
+ add_build_scripts_to_package_json
90
+ remove_static_title_from_index_html
91
+ oxlint_fix
96
92
  git_add_all
97
93
  git_commit_generated_files
98
94
  git_branch_main
@@ -104,18 +100,52 @@ module Foobara
104
100
  end
105
101
  end
106
102
 
107
- def fix_uncorrectable_lint_violations
108
- Dir.chdir(project_directory) do
109
- web_vitals_contents = File.read("src/reportWebVitals.ts")
110
- web_vitals_contents.gsub!("if (onPerfEntry && ", "if (onPerfEntry != null && ")
111
- File.write("src/reportWebVitals.ts", web_vitals_contents)
103
+ def npm_install_react_router
104
+ puts "installing react-router..."
105
+
106
+ cmd = "npm install react-router-dom"
107
+
108
+ Dir.chdir project_directory do
109
+ run_cmd_and_write_output(cmd)
112
110
  end
113
111
  end
114
112
 
115
- def eslint_fix
113
+ def npm_install_sitemap_and_prerender_dependencies
114
+ puts "installing sitemap/pre-render dependencies..."
115
+
116
+ cmd = "npm install --save-dev puppeteer tsx @types/node"
117
+
118
+ Dir.chdir project_directory do
119
+ run_cmd_and_write_output(cmd)
120
+ end
121
+ end
122
+
123
+ def add_build_scripts_to_package_json
124
+ puts "adding prebuild/postbuild scripts..."
125
+
126
+ Dir.chdir project_directory do
127
+ run_cmd_and_write_output(
128
+ 'npm pkg set scripts.prebuild="npx puppeteer browsers install chrome && ' \
129
+ 'tsx scripts/generate-sitemap.ts"'
130
+ )
131
+ run_cmd_and_write_output(
132
+ 'npm pkg set scripts.postbuild="tsx scripts/prerender.ts"'
133
+ )
134
+ end
135
+ end
136
+
137
+ # Pages now set their own <title> in React 19
138
+ def remove_static_title_from_index_html
139
+ index_html_path = File.join(project_directory, "index.html")
140
+ contents = File.read(index_html_path)
141
+
142
+ File.write(index_html_path, contents.sub(%r{\s*<title>.*</title>}, ""))
143
+ end
144
+
145
+ def oxlint_fix
116
146
  puts "linting..."
117
147
 
118
- cmd = "npx eslint 'src/**/*.{js,jsx,ts,tsx}' --fix"
148
+ cmd = "npx oxlint --fix"
119
149
  Dir.chdir project_directory do
120
150
  run_cmd_and_write_output(cmd)
121
151
  end
@@ -137,8 +167,8 @@ module Foobara
137
167
  end
138
168
  end
139
169
 
140
- def git_commit_lint_fixes
141
- cmd = "git commit -m 'Make project work with eslint and eslint --fix everything'"
170
+ def git_commit_initial_setup
171
+ cmd = "git commit -m 'Initial Vite project with react-ts template'"
142
172
 
143
173
  Dir.chdir project_directory do
144
174
  run_cmd_and_write_output(cmd, raise_if_fails: false)
@@ -3,16 +3,17 @@ on: push
3
3
  jobs:
4
4
  tests:
5
5
  runs-on: ubuntu-latest
6
- name: run test suite
6
+ name: build and lint
7
7
  steps:
8
- - uses: actions/checkout@v2
8
+ - uses: actions/checkout@v4
9
9
  - name: Use Node.js
10
10
  if: always()
11
- uses: actions/setup-node@v2
11
+ uses: actions/setup-node@v4
12
12
  with:
13
- # how to grab this from .nvmrc??
14
- node-version: "20.9"
13
+ node-version: "20"
15
14
  - name: Install dependencies
16
15
  run: npm install
17
- - name: Run test
18
- run: npm run test
16
+ - name: Lint
17
+ run: npm run lint
18
+ - name: Build
19
+ run: npm run build
@@ -0,0 +1,17 @@
1
+ {
2
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
3
+ "plugins": ["react", "typescript", "oxc"],
4
+ "rules": {
5
+ "react/rules-of-hooks": "error",
6
+ "react/only-export-components": ["warn", { "allowConstantExport": true }]
7
+ },
8
+ "overrides": [
9
+ {
10
+ "files": ["src/domains/**/*.{ts,tsx}"],
11
+ "rules": {
12
+ "typescript/no-explicit-any": "off",
13
+ "typescript/prefer-as-const": "off"
14
+ }
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1 @@
1
+ /* /index.html 200
@@ -0,0 +1,5 @@
1
+ # https://www.robotstxt.org/robotstxt.html
2
+ User-agent: *
3
+ Disallow:
4
+
5
+ Sitemap: <%= site_url %>/sitemap.xml
@@ -0,0 +1,28 @@
1
+ import { writeFileSync } from 'fs'
2
+ import { resolve, dirname } from 'path'
3
+ import { fileURLToPath } from 'url'
4
+ import { SITE_URL, getSitemapPages } from './routes.ts'
5
+
6
+ const __filename = fileURLToPath(import.meta.url)
7
+ const __dirname = dirname(__filename)
8
+
9
+ const allPages = getSitemapPages()
10
+
11
+ const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
12
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
13
+ ${allPages
14
+ .map(
15
+ (page) => ` <url>
16
+ <loc>${SITE_URL}${page.loc}</loc>
17
+ <changefreq>${page.changefreq}</changefreq>
18
+ <priority>${page.priority}</priority>${page.lastmod ? `\n <lastmod>${page.lastmod}</lastmod>` : ''}
19
+ </url>`,
20
+ )
21
+ .join('\n')}
22
+ </urlset>
23
+ `
24
+
25
+ const sitemapPath = resolve(__dirname, '../public/sitemap.xml')
26
+ writeFileSync(sitemapPath, sitemap)
27
+
28
+ console.log(`Generated sitemap with ${allPages.length} URLs`)
@@ -0,0 +1,53 @@
1
+ import { writeFileSync, mkdirSync } from 'fs'
2
+ import { resolve, dirname } from 'path'
3
+ import { fileURLToPath } from 'url'
4
+ import { preview } from 'vite'
5
+ import puppeteer from 'puppeteer'
6
+ import { getRoutePaths } from './routes.ts'
7
+
8
+ const __filename = fileURLToPath(import.meta.url)
9
+ const __dirname = dirname(__filename)
10
+ const projectRoot = resolve(__dirname, '..')
11
+ const distDir = resolve(projectRoot, 'dist')
12
+
13
+ const routes = getRoutePaths()
14
+
15
+ const server = await preview({ root: projectRoot })
16
+ const serverUrl = server.resolvedUrls!.local[0]
17
+
18
+ const browser = await puppeteer.launch({
19
+ args: ['--no-sandbox', '--disable-setuid-sandbox'],
20
+ })
21
+
22
+ // Render everything before writing anything to deal with / prerendering
23
+ const rendered: { route: string; outputPath: string; html: string }[] = []
24
+
25
+ for (const route of routes) {
26
+ const page = await browser.newPage()
27
+
28
+ await page.evaluateOnNewDocument(() => {
29
+ document.addEventListener('prerender-ready', () => {
30
+ ;(window as Window & { __prerenderReady?: boolean }).__prerenderReady = true
31
+ })
32
+ })
33
+
34
+ await page.goto(new URL(route, serverUrl).href, { waitUntil: 'networkidle0' })
35
+ await page.waitForFunction('window.__prerenderReady === true')
36
+
37
+ const outputPath = route === '/' ? 'index.html' : `${route.replace(/^\//, '')}.html`
38
+ rendered.push({ route, outputPath, html: await page.content() })
39
+
40
+ await page.close()
41
+ }
42
+
43
+ for (const { route, outputPath, html } of rendered) {
44
+ const outputFile = resolve(distDir, outputPath)
45
+
46
+ mkdirSync(dirname(outputFile), { recursive: true })
47
+ writeFileSync(outputFile, html)
48
+ console.log(`Pre-rendered ${route} -> dist/${outputPath}`)
49
+ }
50
+
51
+ await browser.close()
52
+ server.httpServer.close()
53
+ process.exit(0)
@@ -0,0 +1,25 @@
1
+ export const SITE_URL = '<%= site_url %>'
2
+
3
+ export interface SitemapPage {
4
+ loc: string
5
+ changefreq: string
6
+ priority: string
7
+ lastmod?: string
8
+ }
9
+
10
+ export const STATIC_PAGES: SitemapPage[] = [
11
+ { loc: '/', changefreq: 'monthly', priority: '1.0' },
12
+ { loc: '/about', changefreq: 'monthly', priority: '0.8' },
13
+ ]
14
+
15
+ export function getDynamicPages(): SitemapPage[] {
16
+ return []
17
+ }
18
+
19
+ export function getSitemapPages(): SitemapPage[] {
20
+ return [...STATIC_PAGES, ...getDynamicPages()]
21
+ }
22
+
23
+ export function getRoutePaths(): string[] {
24
+ return getSitemapPages().map((page) => page.loc)
25
+ }
@@ -0,0 +1,24 @@
1
+ import { NavLink, Route, Routes } from 'react-router-dom'
2
+ import './App.css'
3
+ import Home from './pages/Home'
4
+ import About from './pages/About'
5
+ import NotFound from './pages/NotFound'
6
+
7
+ function App() {
8
+ return (
9
+ <div className="App">
10
+ <nav>
11
+ <NavLink to="/">Home</NavLink> | <NavLink to="/about">About</NavLink>
12
+ </nav>
13
+ <main>
14
+ <Routes>
15
+ <Route path="/" element={<Home />} />
16
+ <Route path="/about" element={<About />} />
17
+ <Route path="*" element={<NotFound />} />
18
+ </Routes>
19
+ </main>
20
+ </div>
21
+ )
22
+ }
23
+
24
+ export default App
@@ -0,0 +1,28 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot, hydrateRoot } from 'react-dom/client'
3
+ import { BrowserRouter } from 'react-router-dom'
4
+ import './index.css'
5
+ import App from './App.tsx'
6
+
7
+ const rootElement = document.getElementById('root')!
8
+
9
+ const app = (
10
+ <StrictMode>
11
+ <BrowserRouter>
12
+ <App />
13
+ </BrowserRouter>
14
+ </StrictMode>
15
+ )
16
+
17
+ if (rootElement.hasChildNodes()) {
18
+ hydrateRoot(rootElement, app)
19
+ } else {
20
+ createRoot(rootElement).render(app)
21
+ }
22
+
23
+ // Not sure if this is necessary but gives some extra time for things to settle before pre-rendering
24
+ requestAnimationFrame(() => {
25
+ requestAnimationFrame(() => {
26
+ document.dispatchEvent(new Event('prerender-ready'))
27
+ })
28
+ })
@@ -0,0 +1,12 @@
1
+ function About() {
2
+ return (
3
+ <div>
4
+ <title>About</title>
5
+ <meta name="description" content="About this project" />
6
+ <h1>About</h1>
7
+ <p>Stuff about this project or whatever</p>
8
+ </div>
9
+ )
10
+ }
11
+
12
+ export default About
@@ -0,0 +1,12 @@
1
+ function Home() {
2
+ return (
3
+ <div>
4
+ <title>Home</title>
5
+ <meta name="description" content="The home page" />
6
+ <h1>Home</h1>
7
+ <p>Hello, World!</p>
8
+ </div>
9
+ )
10
+ }
11
+
12
+ export default Home
@@ -0,0 +1,11 @@
1
+ function NotFound() {
2
+ return (
3
+ <div>
4
+ <title>Not Found</title>
5
+ <h1>Not Found</h1>
6
+ <p>There is nothing at this URL.</p>
7
+ </div>
8
+ )
9
+ }
10
+
11
+ export default NotFound
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foobara-empty-typescript-react-project-generator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miles Georgi
@@ -58,11 +58,22 @@ files:
58
58
  - src/empty_typescript_react_project_config.rb
59
59
  - src/empty_typescript_react_project_generator.rb
60
60
  - src/generate_empty_typescript_react_project.rb
61
+ - src/generators/robots_txt_generator.rb
62
+ - src/generators/routes_ts_generator.rb
61
63
  - src/write_empty_typescript_react_project_to_disk.rb
62
64
  - templates/.env
63
- - templates/.eslintrc.js
64
65
  - templates/.github/workflows/tests.yml
65
- - templates/tsconfig.json
66
+ - templates/.oxlintrc.json
67
+ - templates/public/_redirects
68
+ - templates/public/robots.txt.erb
69
+ - templates/scripts/generate-sitemap.ts
70
+ - templates/scripts/prerender.ts
71
+ - templates/scripts/routes.ts.erb
72
+ - templates/src/App.tsx
73
+ - templates/src/main.tsx
74
+ - templates/src/pages/About.tsx
75
+ - templates/src/pages/Home.tsx
76
+ - templates/src/pages/NotFound.tsx
66
77
  homepage: https://github.com/foobara/generators-empty-typescript-react-project-generator
67
78
  licenses:
68
79
  - Apache-2.0
@@ -80,13 +91,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
80
91
  - - ">="
81
92
  - !ruby/object:Gem::Version
82
93
  version: 3.4.0
94
+ - - "<"
95
+ - !ruby/object:Gem::Version
96
+ version: 4.1.0
83
97
  required_rubygems_version: !ruby/object:Gem::Requirement
84
98
  requirements:
85
99
  - - ">="
86
100
  - !ruby/object:Gem::Version
87
101
  version: '0'
88
102
  requirements: []
89
- rubygems_version: 3.6.9
103
+ rubygems_version: 4.0.10
90
104
  specification_version: 4
91
105
  summary: Generates empty typescript react projects
92
106
  test_files: []
@@ -1,45 +0,0 @@
1
- module.exports = {
2
- "env": {
3
- "browser": true,
4
- "es2021": true
5
- },
6
- "extends": [
7
- "standard-with-typescript",
8
- "plugin:react/recommended"
9
- ],
10
- "overrides": [
11
- {
12
- "env": {
13
- "node": true
14
- },
15
- "files": [
16
- ".eslintrc.{js,cjs}"
17
- ],
18
- "parserOptions": {
19
- "sourceType": "script"
20
- }
21
- }
22
- ],
23
- parser: '@typescript-eslint/parser',
24
- "parserOptions": {
25
- "ecmaVersion": "latest",
26
- project: ['./tsconfig.json'],
27
- "sourceType": "module"
28
- },
29
- "plugins": [
30
- "react"
31
- ],
32
- settings: {
33
- react: {
34
- version: "detect"
35
- },
36
- },
37
- "rules": {
38
- "@typescript-eslint/explicit-function-return-type": "off",
39
- "@typescript-eslint/triple-slash-reference": "off",
40
- "@typescript-eslint/explicit-function-return-type": "off",
41
- "@typescript-eslint/no-floating-promises": "off",
42
- "react/jsx-indent" : ["error", 2],
43
- "react/jsx-indent-props": ["error", 2]
44
- }
45
- }
@@ -1,26 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "es6",
4
- "lib": [
5
- "dom",
6
- "dom.iterable",
7
- "esnext"
8
- ],
9
- "allowJs": true,
10
- "skipLibCheck": true,
11
- "esModuleInterop": true,
12
- "allowSyntheticDefaultImports": true,
13
- "strict": true,
14
- "forceConsistentCasingInFileNames": true,
15
- "noFallthroughCasesInSwitch": true,
16
- "module": "CommonJS",
17
- "moduleResolution": "node",
18
- "resolveJsonModule": true,
19
- "isolatedModules": true,
20
- "noEmit": true,
21
- "jsx": "react-jsx"
22
- },
23
- "include": [
24
- "src"
25
- ]
26
- }