diff --git a/.alexignore b/.alexignore deleted file mode 100644 index 0167c31e931..00000000000 --- a/.alexignore +++ /dev/null @@ -1,4 +0,0 @@ -build - -CHANGELOG* -CODE_OF_CONDUCT.md diff --git a/.alexrc b/.alexrc deleted file mode 100644 index 8cda2010b9d..00000000000 --- a/.alexrc +++ /dev/null @@ -1,18 +0,0 @@ -{ - "allow": [ - "attacks", - "colors", - "conservative", - "crashes", - "executed", - "executes", - "execution", - "failed", - "hook", - "host-hostess", - "hostesses-hosts", - "invalid", - "remain", - "watchman-watchwoman" - ] -} diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 2628bc87bab..00000000000 --- a/.eslintignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules/ -build/ -test/fixtures/webpack-message-formatting/src/AppBabel.js -packages/react-error-overlay/lib/ diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 7d6e9efcca0..00000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "extends": "eslint:recommended", - "env": { - "browser": true, - "commonjs": true, - "node": true, - "es6": true, - "jest": true - }, - "parserOptions": { - "ecmaVersion": 2018 - }, - "rules": { - "no-console": "off", - "strict": ["error", "global"], - "curly": "warn" - }, - "overrides": [ - { - "files": [ - "docusaurus/website/src/**/*.js", - "packages/cra-template/**/*.js", - "packages/react-error-overlay/**/*.js", - "packages/react-scripts/fixtures/kitchensink/template/{src,integration}/**/*.js", - "test/fixtures/*/src/*.js" - ], - "excludedFiles": ["packages/react-error-overlay/*.js"], - "extends": ["react-app", "react-app/jest"] - }, - { - "files": [ - "test/fixtures/webpack-message-formatting/src/{AppLintError,AppLintWarning,AppUnknownFile}.js" - ], - "rules": { - "no-unused-vars": "off", - "no-undef": "off" - } - }, - { - "files": ["test/fixtures/webpack-message-formatting/src/Export5.js"], - "rules": { - "import/no-anonymous-default-export": "off" - } - }, - { - "files": ["test/fixtures/issue-5176-flow-class-properties/src/App.js"], - "rules": { - "no-dupe-class-members": "off" - } - } - ] -} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 77ceec87b2b..00000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,2 +0,0 @@ -packages/ @iansu @mrmckeb -docusaurus/ @iansu @mrmckeb diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 7c05e921500..00000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -open_collective: create-react-app diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 0f391681bef..00000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,161 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -labels: 'issue: bug report, needs triage' ---- - - - -### Describe the bug - -(Write your answer here.) - -### Did you try recovering your dependencies? - - - -(Write your answer here.) - -### Which terms did you search for in User Guide? - - - -(Write your answer here if relevant.) - -### Environment - - - -(paste the output of the command here.) - -### Steps to reproduce - - - -(Write your steps here:) - -1. -2. -3. - -### Expected behavior - - - -(Write what you thought would happen.) - -### Actual behavior - - - -(Write what happened. Please add screenshots!) - -### Reproducible demo - - - -(Paste the link to an example project and exact instructions to reproduce the issue.) - - diff --git a/.github/ISSUE_TEMPLATE/proposal.md b/.github/ISSUE_TEMPLATE/proposal.md deleted file mode 100644 index 0318fa73620..00000000000 --- a/.github/ISSUE_TEMPLATE/proposal.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -name: Proposal -about: Suggest an idea for improving Create React App -labels: 'issue: proposal, needs triage' ---- - -### Is your proposal related to a problem? - - - -(Write your answer here.) - -### Describe the solution you'd like - - - -(Describe your proposed solution here.) - -### Describe alternatives you've considered - - - -(Write your answer here.) - -### Additional context - - - -(Write your answer here.) diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md deleted file mode 100644 index e547baf9d3f..00000000000 --- a/.github/ISSUE_TEMPLATE/question.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -name: Question -about: Get help with Create React App -labels: 'needs triage' ---- - -If you have a general question about Create React App or about building an app with Create React App we encourage you to post in GitHub Discussions instead of this issue tracker. The maintainers and other community members can provide help and answer your questions there: https://github.com/facebook/create-react-app/discussions - -If you're looking for general information on using React, the React docs have a list of resources: https://reactjs.org/community/support.html - -If you've discovered a bug or would like to propose a change please use one of the other issue templates. - -Thanks! diff --git a/.github/lock.yml b/.github/lock.yml deleted file mode 100644 index 1815cbffcec..00000000000 --- a/.github/lock.yml +++ /dev/null @@ -1,11 +0,0 @@ -# Configuration for lock-threads - https://github.com/dessant/lock-threads - -# Number of days of inactivity before a closed issue or pull request is locked -daysUntilLock: 5 - -# Issues and pull requests with these labels will not be locked. Set to `[]` to disable -exemptLabels: [] - -# Do not comment when locking -setLockReason: false -lockComment: false diff --git a/.github/scripts/find-panel-dir.sh b/.github/scripts/find-panel-dir.sh new file mode 100755 index 00000000000..996f33782e0 --- /dev/null +++ b/.github/scripts/find-panel-dir.sh @@ -0,0 +1,253 @@ +#!/usr/bin/env bash +# +# Finds the directory that holds the admin panel, by looking for config.php. +# +# Why this exists: an FTP path is walked from the account's *login* directory, +# which is not the filesystem root. The path hPanel's File Manager shows -- +# /home/uNNNNNNN/domains/.../public_html/admin -- is therefore not the path the +# deploy needs, and there is no way to convert one into the other from the +# outside. Every attempt to guess it cost a round trip through a human, and the +# guesses that "worked" were worse than the ones that failed: the deploy action +# creates directories it cannot find, so a wrong path silently produced a second +# copy of the whole panel in a folder nobody serves. +# +# So stop guessing and go look. config.php is the fingerprint: it holds the +# database password, it is gitignored, and no deploy has ever written it -- so +# it exists only where the panel was really installed. +# +# Prints the panel's path, relative to the login directory and with a trailing +# slash, on stdout. Everything else goes to stderr, so the caller can capture +# the path alone. Exits non-zero if there is not exactly one answer. +# +# Directory names go to the log; credentials never do. This repository is +# public. + +set -uo pipefail + +: "${FTP_SERVER:?FTP_SERVER is not set}" +: "${FTP_USERNAME:?FTP_USERNAME is not set}" +: "${FTP_PASSWORD:?FTP_PASSWORD is not set}" + +# A hint, not an instruction. When the search turns up more than one candidate +# -- which means there are stale copies on the server -- a hint that matches one +# of them breaks the tie. It can never point the deploy at a directory that has +# no config.php in it, which is the whole failure mode this replaces. +hint="${FTP_REMOTE_DIR:-}" + +max_depth="${PANEL_SEARCH_MAX_DEPTH:-3}" +max_listings="${PANEL_SEARCH_MAX_LISTINGS:-40}" + +# --ssl-reqd encrypts the connection; --insecure skips verifying the server's +# certificate. The second half is a real weakening and it is here because the +# deploy step this guards already does exactly the same thing: Hostinger's FTP +# host presents a certificate curl will not validate, and SamKirkland's action +# completes FTPS against it regardless. A check stricter than the deploy it +# guards blocks the work without protecting anything -- an earlier version did +# precisely that, reporting a folder as missing when it had only been refused +# the handshake. The fix that would let this be strict is a valid certificate +# on the FTP host, which is Hostinger's to provide. +# +# Overridable so the test suite can drive the same code against a local +# plain-FTP fixture. Nothing in CI sets it. +curl_tls="${PANEL_SEARCH_CURL_TLS---ssl-reqd --insecure}" + +cfg="$(mktemp)" +trap 'rm -f "$cfg" "$cfg.err"' EXIT + +listings=0 +list_err="" + +# Percent-encodes a path for a URL, leaving the separators alone. Without this +# a folder whose name contains a space -- ordinary on this server, which has +# "background car.webp" sitting in the web root -- makes curl reject the URL as +# malformed, and the search reports the panel missing when it never looked. +# LC_ALL=C makes the loop walk bytes, so multi-byte names encode correctly. +urlenc() { + local LC_ALL=C s="$1" out="" c i + for (( i = 0; i < ${#s}; i++ )); do + c="${s:i:1}" + case "$c" in + [a-zA-Z0-9._~/-]) out+="$c" ;; + *) out+="$(printf '%%%02X' "'$c")" ;; + esac + done + printf '%s' "$out" +} + +# Lists one directory. $1 is a path relative to the login directory, with a +# trailing slash; empty means the login directory itself. Credentials go in a +# config file rather than on the command line, which would put the password in +# the process list. +ftp_list() { + printf 'user = "%s:%s"\nurl = "ftp://%s/%s"\n' \ + "$FTP_USERNAME" "$FTP_PASSWORD" "$FTP_SERVER" "$(urlenc "$1")" > "$cfg" + # shellcheck disable=SC2086 # curl_tls is a deliberate word-split list + curl --silent --show-error $curl_tls --connect-timeout 20 --max-time 60 \ + --config "$cfg" 2>"$cfg.err" +} + +# Pulls names out of a raw LIST response. $1 is the listing, $2 is 'd' for +# directories or 'f' for everything else. The name is every field from the +# ninth on, so names containing spaces ("background car.webp") survive. +names() { + printf '%s\n' "$1" | awk -v want="$2" ' + NF >= 9 { + kind = (substr($1, 1, 1) == "d") ? "d" : "f" + if (kind != want) next + name = $0 + for (i = 1; i <= 8; i++) sub(/^[^ \t]+[ \t]+/, "", name) + if (name == "." || name == "..") next + print name + }' +} + +# Breadth-first, so the shallowest copy is found first and a nested one cannot +# shadow it. A directory holding config.php is not descended into: the panel's +# own subfolders are not more panels. +queue=("") +found=() +map=() +depth=0 +truncated=0 + +while [ "$depth" -le "$max_depth" ] && [ "${#queue[@]}" -gt 0 ]; do + next=() + for path in "${queue[@]}"; do + if [ "$listings" -ge "$max_listings" ]; then + truncated=1 + break + fi + + out="$(ftp_list "$path")" + rc=$? + listings=$((listings + 1)) + if [ "$rc" -ne 0 ]; then + # A directory that cannot be listed is not a failure of the search -- + # permissions vary across a shared host. Only the login directory failing + # is fatal, and that is checked after the loop. + [ -z "$list_err" ] && list_err="$(cat "$cfg.err" 2>/dev/null)" + continue + fi + + # A map of what was actually seen. "Not found" on its own says nothing + # about whether the search was in the right neighbourhood; a directory + # holding index.php and the panel's own subfolders is worth knowing about + # even when config.php is missing from it, because that combination means + # the files are there and only the one gitignored file is not. + marks="" + printf '%s\n' "$out" | grep -q ' index\.php$' && marks="$marks index.php" + for sub in api src sql tools; do + names "$out" d | grep -qx "$sub" && marks="$marks $sub/" + done + marks="${marks# }" + file_count="$(names "$out" f | grep -c . || true)" + map+=("${path:-./}|${file_count} files|${marks:-nothing recognisable}") + + if names "$out" f | grep -qx 'config.php'; then + found+=("$path") + continue + fi + + while IFS= read -r dir; do + [ -n "$dir" ] || continue + case "$dir" in + .*) continue ;; # .git, .well-known, and friends + node_modules|vendor|cache|tmp) continue ;; + esac + next+=("$path$dir/") + done < <(names "$out" d) + done + + [ "$truncated" -eq 1 ] && break + queue=("${next[@]}") + depth=$((depth + 1)) +done + +# Compares a found path against the hint. The hint is whatever someone typed +# into the secret, so it may carry a leading slash, a filesystem prefix, or +# neither; matching on the tail is the only comparison that holds up. +matches_hint() { + local candidate="${1%/}" want="${hint%/}" + candidate="${candidate#/}" + want="${want#/}" + [ -n "$want" ] || return 1 + [ "$candidate" = "$want" ] || [ "${want%"/$candidate"}" != "$want" ] +} + +if [ "${#found[@]}" -eq 0 ]; then + echo "::error::Could not find the admin panel on the server." >&2 + echo "" >&2 + echo "Searched $listings directories, $max_depth levels out from the FTP" >&2 + echo "account's login directory. None contains config.php, so there is no" >&2 + echo "folder reachable from here that is the panel." >&2 + # Whether the search ran out of budget changes what this result means: a + # completed search proves the panel is not reachable, a truncated one only + # says it was not found yet. Reporting "not found" for both would be the + # same class of mistake as reporting a failed listing as an empty folder. + if [ "$truncated" -eq 1 ]; then + echo "" >&2 + echo "The search stopped early at its $max_listings-directory limit, so this" >&2 + echo "is not proof the panel is absent -- only that it was not found yet." >&2 + fi + if [ -n "$list_err" ]; then + echo "" >&2 + echo "curl reported at least one error while looking:" >&2 + printf '%s\n' "$list_err" | head -5 | sed 's/^/ /' >&2 + fi + echo "" >&2 + echo "Every directory it looked in, and what was in each:" >&2 + echo "" >&2 + for entry in "${map[@]}"; do + IFS='|' read -r where count marks <<< "$entry" + printf ' %-44s %-10s %s\n' "$where" "$count" "$marks" >&2 + done + echo "" >&2 + echo "A row listing index.php alongside api/ src/ is this panel's own layout." >&2 + echo "If one of those appears above, the panel's files are there and only" >&2 + echo "config.php is missing from it -- which is what a copy made by a deploy" >&2 + echo "looks like, since config.php is excluded from every upload." >&2 + echo "" >&2 + echo "The account logs in here, and this is what it can see:" >&2 + echo "" >&2 + home="$(ftp_list "")" + if [ $? -eq 0 ] && [ -n "$home" ]; then + printf '%s\n' "$home" | awk 'NF >= 9 { name = $0; for (i = 1; i <= 8; i++) sub(/^[^ \t]+[ \t]+/, "", name); if (name != "." && name != "..") print (substr($1,1,1) == "d" ? " " name "/" : " " name) }' | head -30 >&2 + else + echo " (the login directory could not be listed at all -- this looks like" >&2 + echo " an account or connection problem, not a wrong path)" >&2 + fi + echo "" >&2 + echo "If the panel is not reachable from here, the FTP account is rooted in the" >&2 + echo "wrong place. In hPanel -> Files -> FTP Accounts, the account's Directory" >&2 + echo "must be at or above the folder holding config.php." >&2 + exit 1 +fi + +if [ "${#found[@]}" -gt 1 ]; then + for candidate in "${found[@]}"; do + if matches_hint "$candidate"; then + echo "More than one folder here contains config.php; FTP_REMOTE_DIR picks one:" >&2 + printf ' %s\n' "${found[@]}" >&2 + printf '%s\n' "${candidate:-./}" + exit 0 + fi + done + + echo "::error::More than one folder on the server contains config.php." >&2 + echo "" >&2 + printf ' %s\n' "${found[@]}" >&2 + echo "" >&2 + echo "These are almost certainly stale copies left by an earlier deploy that" >&2 + echo "ran against a wrong path. Delete the ones that are not served, or set" >&2 + echo "FTP_REMOTE_DIR to the one that is, and run this again." >&2 + echo "" >&2 + echo "Refusing to guess: deploying into the wrong one leaves the live panel" >&2 + echo "unchanged while the run reports success." >&2 + exit 1 +fi + +echo "Found the panel by its config.php, at: ${found[0]:-the login directory}" >&2 +if [ "$truncated" -eq 1 ]; then + echo "(the search stopped at $max_listings directories)" >&2 +fi +printf '%s\n' "${found[0]:-./}" diff --git a/.github/scripts/find-panel-dir.test.sh b/.github/scripts/find-panel-dir.test.sh new file mode 100755 index 00000000000..41aae81f7f3 --- /dev/null +++ b/.github/scripts/find-panel-dir.test.sh @@ -0,0 +1,130 @@ +#!/usr/bin/env bash +# +# Exercises find-panel-dir.sh against a local FTP server whose directory trees +# mirror the shapes this has actually hit on the real host. +# +# Needs python3 with pyftpdlib: pip install pyftpdlib +# Run from the repository root: .github/scripts/find-panel-dir.test.sh +# +# The fixture speaks plain FTP, so PANEL_SEARCH_CURL_TLS is emptied for the +# duration. That is the one behaviour these cases cannot cover; everything the +# script decides -- which directory wins, when to refuse -- is exercised here. + +set -uo pipefail + +script="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/find-panel-dir.sh" +work="$(mktemp -d)" +trap 'rm -rf "$work"' EXIT + +cat > "$work/server.py" <<'PY' +import sys +from pyftpdlib.authorizers import DummyAuthorizer +from pyftpdlib.handlers import FTPHandler +from pyftpdlib.servers import FTPServer + +auth = DummyAuthorizer() +auth.add_user("tester", "secret", sys.argv[1], perm="elradfmw") +FTPHandler.authorizer = auth +FTPServer(("127.0.0.1", int(sys.argv[2])), FTPHandler).serve_forever() +PY + +pass=0 +fail=0 +rc=0 +out="" +err="" + +run() { # $1 = function that builds the tree under $root + root="$work/root" + rm -rf "$root" + mkdir -p "$root" + "$1" + + port=$((21000 + RANDOM % 2000)) + python3 "$work/server.py" "$root" "$port" >/dev/null 2>&1 & + local pid=$! + for _ in $(seq 40); do + (exec 3<>/dev/tcp/127.0.0.1/$port) 2>/dev/null && break + sleep 0.1 + done + + err="$(FTP_SERVER="127.0.0.1:$port" FTP_USERNAME=tester FTP_PASSWORD=secret \ + FTP_REMOTE_DIR="${HINT:-}" PANEL_SEARCH_CURL_TLS= \ + bash "$script" 2>&1 >"$work/out")" + rc=$? + out="$(cat "$work/out")" + + kill "$pid" 2>/dev/null + wait "$pid" 2>/dev/null +} + +check() { # $1 = name, $2 = expected exit code, $3 = expected stdout + if [ "$rc" = "$2" ] && [ "$out" = "$3" ]; then + echo " PASS $1" + pass=$((pass + 1)) + else + echo " FAIL $1" + echo " exit $rc (wanted $2), printed '$out' (wanted '$3')" + printf '%s\n' "$err" | head -12 | sed 's/^/ | /' + fail=$((fail + 1)) + fi +} + +# The shape the live server actually has: the account lands in the main site's +# web root, and the panel is one folder down among the site's own files. +tree_real() { + mkdir -p "$root/admin.niteshacars.in/api" "$root/admin.niteshacars.in/src" + touch "$root/admin.niteshacars.in/config.php" "$root/admin.niteshacars.in/index.php" + touch "$root/asset-manifest.json" "$root/background car.webp" +} +HINT="/domains/admin.niteshacars.in/public_html/" run tree_real +check "finds the panel despite a stale hint" 0 "admin.niteshacars.in/" + +tree_at_root() { touch "$root/config.php" "$root/index.php"; mkdir -p "$root/api"; } +HINT="" run tree_at_root +check "account rooted at the panel itself" 0 "./" + +tree_deep() { + mkdir -p "$root/domains/admin.niteshacars.in/public_html" + touch "$root/domains/admin.niteshacars.in/public_html/config.php" +} +HINT="" run tree_deep +check "panel three levels down" 0 "domains/admin.niteshacars.in/public_html/" + +tree_none() { mkdir -p "$root/images"; touch "$root/index.html"; } +HINT="" run tree_none +check "no panel anywhere: refuses" 1 "" + +# Two copies is the state an earlier wrong path leaves behind. Picking one +# would put the deploy back where it started, so it refuses without a hint. +tree_two() { + mkdir -p "$root/admin.niteshacars.in" "$root/domains/admin.niteshacars.in/public_html" + touch "$root/admin.niteshacars.in/config.php" + touch "$root/domains/admin.niteshacars.in/public_html/config.php" +} +HINT="" run tree_two +check "two copies, no hint: refuses" 1 "" + +HINT="/domains/admin.niteshacars.in/public_html/" run tree_two +check "two copies, hint breaks the tie" 0 "domains/admin.niteshacars.in/public_html/" + +tree_nested() { + mkdir -p "$root/panel/src" + touch "$root/panel/config.php" "$root/panel/src/config.php" +} +HINT="" run tree_nested +check "a subfolder cannot shadow the panel" 0 "panel/" + +# "background car.webp" is really sitting in that web root, so names with +# spaces are not hypothetical. Unencoded, curl rejects the URL outright and +# the search reports the panel missing. +tree_spaces() { + mkdir -p "$root/my admin folder" + touch "$root/my admin folder/config.php" "$root/background car.webp" +} +HINT="" run tree_spaces +check "directory name containing spaces" 0 "my admin folder/" + +echo "" +echo " $pass passed, $fail failed" +[ "$fail" -eq 0 ] diff --git a/.github/scripts/write-admin-config.sh b/.github/scripts/write-admin-config.sh new file mode 100755 index 00000000000..504ab914af5 --- /dev/null +++ b/.github/scripts/write-admin-config.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +# +# Writes the admin panel's config.php from environment variables. +# +# This file used to be placed by hand and never deployed, which made it a +# reliable marker for "the real panel is here". That was worth something while +# the panel's location was unknown. It stopped being worth the cost once the +# only remaining task was copying one 577-byte file between two directories +# that look identical in two different file browsers -- which took most of a +# day and never succeeded. +# +# The credentials live in GitHub Actions secrets and are written here at +# deploy time. Nothing sensitive is committed, and nothing is printed: the +# values reach the file through the environment and are never echoed, so a +# public build log cannot leak them. +# +# Usage: DB_HOST=... DB_NAME=... DB_USER=... DB_PASSWORD=... \ +# write-admin-config.sh + +set -uo pipefail + +out="${1:?usage: write-admin-config.sh }" + +: "${DB_NAME:?DB_NAME is not set}" +: "${DB_USER:?DB_USER is not set}" +: "${DB_PASSWORD:?DB_PASSWORD is not set}" + +host="${DB_HOST:-localhost}" +charset="${DB_CHARSET:-utf8mb4}" +storage="${STORAGE_PATH:-__DIR__ . '/../nitesha-storage'}" +idle="${SESSION_IDLE_MINUTES:-120}" +https_only="${HTTPS_ONLY:-true}" + +# PHP single-quoted strings take exactly two escapes: a backslash and a single +# quote. Anything else is literal. Without this a password containing an +# apostrophe writes a config.php that is a PHP syntax error -- the panel would +# answer every request with a blank page, and the cause would not be visible +# from anywhere. +php_str() { + printf '%s' "$1" | sed "s/\\\\/\\\\\\\\/g; s/'/\\\\'/g" +} + +# The origin list is only consulted for cross-origin requests. The site and the +# panel now share a host, so this is belt and braces -- but it costs nothing and +# keeps the panel usable if the two are ever split across hosts again. +origins="${PUBLIC_SITE_ORIGIN:-https://niteshacars.in,https://www.niteshacars.in}" +origin_lines="" +IFS=',' read -ra parts <<< "$origins" +for origin in "${parts[@]}"; do + origin="$(printf '%s' "$origin" | tr -d '[:space:]')" + [ -n "$origin" ] || continue + origin_lines="${origin_lines} '$(php_str "$origin")',"$'\n' +done + +umask 077 +cat > "$out" < Settings -> Secrets and variables -> Actions instead. + +return [ + 'db' => [ + 'host' => '$(php_str "$host")', + 'name' => '$(php_str "$DB_NAME")', + 'user' => '$(php_str "$DB_USER")', + 'password' => '$(php_str "$DB_PASSWORD")', + 'charset' => '$(php_str "$charset")', + ], + + 'storage_path' => ${storage}, + + 'public_site_origin' => [ +${origin_lines} ], + + 'https_only' => ${https_only}, + + 'session_idle_minutes' => ${idle}, +]; +PHP + +# Reports shape, never contents. A build log on a public repository is public. +echo "write-admin-config: wrote $(wc -c < "$out") bytes to $out" diff --git a/.github/scripts/write-admin-config.test.sh b/.github/scripts/write-admin-config.test.sh new file mode 100755 index 00000000000..f47e7aac589 --- /dev/null +++ b/.github/scripts/write-admin-config.test.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +# +# Checks that the generated config.php is valid PHP and carries the values +# through intact -- including the ones that break naive quoting. +# +# Needs php-cli. Run from the repository root. + +set -uo pipefail + +script="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/write-admin-config.sh" +work="$(mktemp -d)" +trap 'rm -rf "$work"' EXIT + +pass=0 +fail=0 + +check() { # name, password, expected-read-back + local name="$1" password="$2" + local out="$work/config.php" + + DB_HOST=localhost DB_NAME=u123_db DB_USER=u123_user DB_PASSWORD="$password" \ + bash "$script" "$out" >/dev/null 2>&1 + + if ! php -l "$out" >/dev/null 2>&1; then + echo " FAIL $name — generated file is not valid PHP" + php -l "$out" 2>&1 | head -3 | sed 's/^/ /' + fail=$((fail + 1)) + return + fi + + # Read it back the way the panel does, and compare to what went in. + local got + got="$(php -r '$c = require $argv[1]; echo $c["db"]["password"];' "$out" 2>/dev/null)" + if [ "$got" = "$password" ]; then + echo " PASS $name" + pass=$((pass + 1)) + else + echo " FAIL $name — read back [$got], expected [$password]" + fail=$((fail + 1)) + fi +} + +check "ordinary password" 'Str0ngPass' +check "apostrophe" "it's-a-pass'word" +check "backslash" 'back\slash\\double' +check "both, adjacent" "\\'mixed\\'" +check "dollar and braces" 'a$b${c}d`e`' +check "double quotes" 'say "hello" now' +check "leading and trailing space" ' padded ' +check "unicode" 'pässwörd-தமிழ்' + +# The structural fields must survive too, not just the password. +out="$work/config.php" +DB_HOST=db.example.net DB_NAME=mydb DB_USER=myuser DB_PASSWORD=pw \ + PUBLIC_SITE_ORIGIN='https://a.test, https://b.test' HTTPS_ONLY=true \ + bash "$script" "$out" >/dev/null 2>&1 +shape="$(php -r ' + $c = require $argv[1]; + echo $c["db"]["host"], "|", $c["db"]["name"], "|", + implode(",", $c["public_site_origin"]), "|", + var_export($c["https_only"], true), "|", + $c["session_idle_minutes"]; +' "$out" 2>/dev/null)" +want='db.example.net|mydb|https://a.test,https://b.test|true|120' +if [ "$shape" = "$want" ]; then + echo " PASS host, name, origins, https_only and idle carry through" + pass=$((pass + 1)) +else + echo " FAIL structure — got [$shape], wanted [$want]" + fail=$((fail + 1)) +fi + +# The panel reads storage_path as a path relative to its own directory. +if php -r '$c = require $argv[1]; exit(str_ends_with($c["storage_path"], "/../nitesha-storage") ? 0 : 1);' "$out"; then + echo " PASS storage_path resolves relative to the panel" + pass=$((pass + 1)) +else + echo " FAIL storage_path did not resolve as expected" + fail=$((fail + 1)) +fi + +# A public build log must never carry the password. +log="$(DB_NAME=d DB_USER=u DB_PASSWORD='SUPERSECRET123' bash "$script" "$work/c2.php" 2>&1)" +if printf '%s' "$log" | grep -q 'SUPERSECRET123'; then + echo " FAIL the password appeared in the script's own output" + fail=$((fail + 1)) +else + echo " PASS nothing sensitive is printed" + pass=$((pass + 1)) +fi + +echo "" +echo " $pass passed, $fail failed" +[ "$fail" -eq 0 ] diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 91807e989b9..00000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,61 +0,0 @@ -# Configuration for probot-stale - https://github.com/probot/stale - -# Number of days of inactivity before an Issue or Pull Request becomes stale -daysUntilStale: 30 - -# Number of days of inactivity before a stale Issue or Pull Request is closed. -# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. -daysUntilClose: 5 - -# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable -exemptLabels: - - 'contributions: claimed' - - 'contributions: up for grabs!' - - 'good first issue' - - 'issue: announcement' - - 'issue: bug' - - 'issue: needs investigation' - - 'issue: proposal' - - 'tag: breaking change' - - 'tag: bug fix' - - 'tag: documentation' - - 'tag: enhancement' - - 'tag: internal' - - 'tag: new feature' - - 'tag: underlying tools' - -# Set to true to ignore issues in a project (defaults to false) -exemptProjects: true - -# Set to true to ignore issues in a milestone (defaults to false) -exemptMilestones: true - -# Label to use when marking as stale -staleLabel: stale - -# Limit the number of actions per hour, from 1-30. Default is 30 -limitPerRun: 30 - -issues: - # Comment to post when marking Issues as stale. - markComment: > - This issue has been automatically marked as stale because it has not had any - recent activity. It will be closed in 5 days if no further activity occurs. - - # Comment to post when closing a stale Issue. - closeComment: > - This issue has been automatically closed because it has not had any recent - activity. If you have a question or comment, please open a new issue. - -pulls: - # Comment to post when marking Pull Request as stale. - markComment: > - This pull request has been automatically marked as stale because it has not - had any recent activity. It will be closed in 5 days if no further activity - occurs. - - # Comment to post when closing a stale Pull Request. - closeComment: > - This pull request has been automatically closed because it has not had any - recent activity. If you have a question or comment, please open a new - issue. Thank you for your contribution! diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml deleted file mode 100644 index ed41799251d..00000000000 --- a/.github/workflows/build-and-test.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: 'Build & Test' - -on: - push: - branches: - - main - pull_request: - branches: - - main - -jobs: - build: - name: 'Build (${{ matrix.os }}, Node ${{ matrix.node }})' - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: - - 'ubuntu-latest' - node: - - '16' - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node }} - cache: 'npm' - - name: Install dependencies - run: npm ci --prefer-offline - - name: Build - run: npm run build - - integration: - name: 'Integration Tests (${{ matrix.os }}, Node ${{ matrix.node }})' - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: - - 'ubuntu-latest' - - 'macos-latest' - - 'windows-latest' - node: - - '16' - steps: - - uses: actions/checkout@v3 - - name: Setup node - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node }} - cache: 'npm' - - name: Install dependencies - run: npm ci --prefer-offline - # The integration tests are run with yarn, so we need to install it. - - name: Install yarn - run: npm i -g yarn - - name: Run integration tests - run: npm run test:integration - - e2e-simple: - name: E2E Simple - uses: ./.github/workflows/e2e-base.yml - with: - testScript: 'tasks/e2e-simple.sh' - - e2e-installs: - name: E2E Installs - uses: ./.github/workflows/e2e-base.yml - with: - testScript: 'tasks/e2e-installs.sh' - - e2e-kitchensink: - name: E2E Kitchensink - uses: ./.github/workflows/e2e-base.yml - with: - testScript: 'tasks/e2e-kitchensink.sh' diff --git a/.github/workflows/deploy-admin.yml b/.github/workflows/deploy-admin.yml new file mode 100644 index 00000000000..7f036d22d6c --- /dev/null +++ b/.github/workflows/deploy-admin.yml @@ -0,0 +1,170 @@ +# Deploys the PHP admin panel to Hostinger over FTPS. +# +# The public site goes to GitHub Pages (deploy-my-app.yml). This is the other +# half: admin.niteshacars.in runs on Hostinger, which has no connection to this +# repository, so api/public-vehicles.php only reaches the server through here. +# +# Credentials live in repository secrets, never in this file: +# Settings -> Secrets and variables -> Actions -> New repository secret +# +# FTP_SERVER ftp.niteshacars.in, or whatever hPanel shows under FTP Accounts +# FTP_USERNAME the FTP account username +# FTP_PASSWORD its password +# FTP_REMOTE_DIR optional. Only needed if more than one copy of the panel +# exists on the server, to say which one is the live one. +# +# The deploy no longer needs to be told where the panel is: it finds the folder +# holding config.php and writes there. An FTP path is walked from the account's +# login directory rather than the filesystem root, so the path hPanel's File +# Manager shows cannot be used directly -- and getting that translation wrong +# was never loud, because the deploy action creates directories it cannot find. +# Three "successful" deploys went into folders nobody served. +# +# NOTE: never commit a real RESET_TOKEN in tools/reset-password.php. This +# repository is public, and the file ships inert on purpose. Set the token by +# editing the file on the server, or upload it by hand when you need it. + +name: Deploy admin panel + +on: + # Push triggers are removed. These workflows upload over FTP to a + # directory Hostinger's own Git deployment clears on every publish, so + # everything they wrote was erased by the next merge -- and each run + # left another stray folder behind on the way. They are kept, manual + # only, because the login-directory listing is still the one reliable + # way to see what is actually on the server. + workflow_dispatch: + inputs: + dry_run: + description: 'List what would change without uploading anything' + type: boolean + default: true + +concurrency: + # Two deploys overlapping on one FTP account leaves a half-written panel. + group: deploy-admin + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Check the secrets are set + env: + FTP_SERVER: ${{ secrets.FTP_SERVER }} + FTP_USERNAME: ${{ secrets.FTP_USERNAME }} + FTP_PASSWORD: ${{ secrets.FTP_PASSWORD }} + FTP_REMOTE_DIR: ${{ secrets.FTP_REMOTE_DIR }} + run: | + # Without this the FTP action fails deep inside itself with a + # connection error, which reads like the server is down rather than + # like a secret was never added. + missing="" + [ -n "$FTP_SERVER" ] || missing="$missing FTP_SERVER" + [ -n "$FTP_USERNAME" ] || missing="$missing FTP_USERNAME" + [ -n "$FTP_PASSWORD" ] || missing="$missing FTP_PASSWORD" + + if [ -n "$missing" ]; then + echo "::error::Missing repository secrets:$missing" + echo "Add them under Settings -> Secrets and variables -> Actions." + exit 1 + fi + + # FTP_REMOTE_DIR is deliberately not in that list. The next step + # locates the panel itself; this secret only breaks a tie when there + # is more than one copy on the server, and its format does not matter + # because it is compared on its tail. + if [ -n "$FTP_REMOTE_DIR" ]; then + echo "FTP_REMOTE_DIR is set; it will be used only to disambiguate." + else + echo "FTP_REMOTE_DIR is not set; the panel will be located by config.php." + fi + + # FTP_SERVER is a bare host -- an IP or a hostname -- and nothing + # else. hPanel displays it as ftp://1.2.3.4, and pasting that whole + # string makes the FTP client look up a *hostname* of "ftp://1.2.3.4", + # which fails as ENOTFOUND: an error that reads like the server is + # missing rather than like the value has a prefix on it. + # + # Nothing below prints the value. This repository is public, so the + # logs are public; only derived facts are reported, never the secret + # or anything that reconstructs it. + bad=0 + case "$FTP_SERVER" in + *://*) echo "::error::FTP_SERVER contains '://'. Remove the ftp:// prefix -- just the host."; bad=1 ;; + esac + case "$FTP_SERVER" in + */*) echo "::error::FTP_SERVER contains '/'. It is a host, not a URL or a path."; bad=1 ;; + esac + case "$FTP_SERVER" in + *:*) echo "::error::FTP_SERVER contains ':'. Drop the port -- 21 is the default."; bad=1 ;; + esac + case "$FTP_SERVER" in + *[[:space:]]*) echo "::error::FTP_SERVER contains a space. A trailing space survives a paste and breaks the lookup."; bad=1 ;; + esac + + echo "FTP_SERVER length: ${#FTP_SERVER} characters" + echo "(a bare IPv4 address is 7 to 15 characters)" + + # Only hostnames get the lookup. An IP literal needs no DNS, and + # checking one with getent is a reverse lookup that fails whenever + # the address has no PTR record -- which is normal for shared + # hosting, so it would reject the correct value. + if [ "$bad" -eq 0 ]; then + if echo "$FTP_SERVER" | grep -Eq '^[0-9]{1,3}(\.[0-9]{1,3}){3}$'; then + echo "FTP_SERVER is an IP address; no lookup needed." + elif getent hosts "$FTP_SERVER" >/dev/null 2>&1; then + echo "FTP_SERVER resolves." + else + echo "::error::FTP_SERVER is neither an IP address nor a hostname DNS knows about." + echo "Check it against hPanel -> Files -> FTP Accounts." + bad=1 + fi + fi + + [ "$bad" -eq 0 ] || exit 1 + + - name: Find the panel on the server + id: panel + env: + FTP_SERVER: ${{ secrets.FTP_SERVER }} + FTP_USERNAME: ${{ secrets.FTP_USERNAME }} + FTP_PASSWORD: ${{ secrets.FTP_PASSWORD }} + FTP_REMOTE_DIR: ${{ secrets.FTP_REMOTE_DIR }} + run: | + # Searches for config.php and prints the folder holding it. See the + # script for why that file is the right fingerprint and why the path + # cannot simply be configured. Fails if there is not exactly one + # answer, rather than picking one -- a wrong choice here is silent. + dir="$(.github/scripts/find-panel-dir.sh)" + echo "dir=$dir" >> "$GITHUB_OUTPUT" + echo "Deploying to: $dir" + + - name: Deploy over FTPS + uses: SamKirkland/FTP-Deploy-Action@v4.3.5 + with: + server: ${{ secrets.FTP_SERVER }} + username: ${{ secrets.FTP_USERNAME }} + password: ${{ secrets.FTP_PASSWORD }} + protocol: ftps + local-dir: ./admin/ + server-dir: ${{ steps.panel.outputs.dir }} + dry-run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run }} + + # Leaves files the action never uploaded alone. Turning this on would + # wipe the folder first and take config.php with it -- the panel would + # stop with "Not set up yet" and the database credentials on the + # server would be gone. + dangerous-clean-slate: false + + # Everything below is either untracked on purpose or meaningless on a + # web server. config.php is the important one: it holds the live + # database password, which is why it is not in git. + exclude: | + config.php + .ftp-deploy-sync-state.json + **/.git* + **/.git*/** + **/.DS_Store diff --git a/.github/workflows/deploy-hostinger.yml b/.github/workflows/deploy-hostinger.yml new file mode 100644 index 00000000000..f8e7e4ed59d --- /dev/null +++ b/.github/workflows/deploy-hostinger.yml @@ -0,0 +1,217 @@ +# Deploys the public website to Hostinger. +# +# The site moved off GitHub Pages so that it and the admin panel could share +# one host: the website at niteshacars.in, the panel at niteshacars.in/admin. +# That was not a preference. The panel is PHP and Pages serves static files +# only, so /admin could not exist there at all -- and putting them on one +# origin also removed the CORS allowlist, the second TLS certificate, and the +# silent failure that came with it. +# +# deploy-admin.yml still deploys the panel. This one deploys the site around +# it, and the two are kept apart by the `exclude` list below. +# +# Secrets are the same three the panel deploy uses (FTP_SERVER, FTP_USERNAME, +# FTP_PASSWORD) -- that account logs in at the website's web root, which is +# exactly where this needs to write. + +name: Deploy site to Hostinger + +on: + # Push triggers are removed. These workflows upload over FTP to a + # directory Hostinger's own Git deployment clears on every publish, so + # everything they wrote was erased by the next merge -- and each run + # left another stray folder behind on the way. They are kept, manual + # only, because the login-directory listing is still the one reliable + # way to see what is actually on the server. + workflow_dispatch: + inputs: + dry_run: + description: 'List what would change without uploading anything' + type: boolean + default: true + +concurrency: + # One FTP account, and the panel deploy uses it too. Two uploads interleaving + # over one connection leaves a half-written site. + group: deploy-hostinger + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22.x + cache: npm + cache-dependency-path: my-app/package-lock.json + + - name: Install + working-directory: my-app + run: npm ci + + - name: Lint + working-directory: my-app + run: npm run lint + + - name: Build + working-directory: my-app + run: npm run build + + - name: Check the secrets are set + env: + FTP_SERVER: ${{ secrets.FTP_SERVER }} + FTP_USERNAME: ${{ secrets.FTP_USERNAME }} + FTP_PASSWORD: ${{ secrets.FTP_PASSWORD }} + run: | + missing="" + [ -n "$FTP_SERVER" ] || missing="$missing FTP_SERVER" + [ -n "$FTP_USERNAME" ] || missing="$missing FTP_USERNAME" + [ -n "$FTP_PASSWORD" ] || missing="$missing FTP_PASSWORD" + if [ -n "$missing" ]; then + echo "::error::Missing repository secrets:$missing" + exit 1 + fi + echo "All three FTP secrets are present." + + - name: Make sure we are not about to write into the panel + env: + FTP_SERVER: ${{ secrets.FTP_SERVER }} + FTP_USERNAME: ${{ secrets.FTP_USERNAME }} + FTP_PASSWORD: ${{ secrets.FTP_PASSWORD }} + run: | + # This deploy writes to the account's login directory, whatever that + # is -- so unlike the panel deploy it has no path to verify. The one + # thing worth ruling out is the account being re-pointed at the panel + # itself: writing a whole website over the panel's index.php would + # take the admin offline, and the upload would report success. + # + # config.php only exists inside the panel, so finding one here means + # the login directory IS the panel. + cfg="$(mktemp)" + trap 'rm -f "$cfg" "$cfg.err"' EXIT + printf 'user = "%s:%s"\nurl = "ftp://%s/"\n' \ + "$FTP_USERNAME" "$FTP_PASSWORD" "$FTP_SERVER" > "$cfg" + + set +e + listing="$(curl --silent --show-error --ssl-reqd --insecure --list-only \ + --connect-timeout 20 --max-time 60 \ + --config "$cfg" 2>"$cfg.err")" + rc=$? + set -e + + if [ "$rc" -ne 0 ]; then + # curl exit 67 is specifically "the server rejected the login", and + # saying so is worth the six lines: the generic message sent two + # runs chasing the wrong thing, because "could not list the + # directory" reads like a network or path problem when it is + # actually a username and password the host no longer accepts. + if [ "$rc" -eq 67 ]; then + echo "::error::The FTP server rejected the username and password." + echo "" + echo "The three secrets are set, they are just no longer valid --" + echo "an account renamed, deleted, or its password changed." + echo "" + echo " 1. hPanel -> Files -> FTP Accounts: note the hostname and" + echo " username, and set a new password if you do not have it." + echo " 2. Check the account's directory is the website root, the" + echo " folder holding index.html. This deploy writes to" + echo " wherever the account lands when it logs in." + echo " 3. Update FTP_SERVER, FTP_USERNAME and FTP_PASSWORD under" + echo " Settings -> Secrets and variables -> Actions." + else + echo "::error::Could not list the FTP login directory, so the target is unverified." + fi + sed 's/^/ /' "$cfg.err" | head -5 + exit 1 + fi + + if printf '%s' "$listing" | grep -qx 'config.php'; then + echo "::error::The FTP account logs straight into the admin panel." + echo "" + echo "Deploying the website here would overwrite the panel. The account" + echo "should log in at the website's web root, with the panel in admin/" + echo "below it." + exit 1 + fi + + # Sorted and shown in full rather than the first 20 in whatever + # order the server felt like. A truncated, unsorted listing of a + # directory with dozens of entries answers no question you actually + # have -- it cost a round trip to work that out. + echo "Login directory is not the panel. It currently holds:" + printf '%s\n' "$listing" | grep -vx '\.' | sort | sed 's/^/ /' + echo "" + echo "(the website's files are written here, alongside whatever else is above)" + + - name: Deploy over FTPS + uses: SamKirkland/FTP-Deploy-Action@v4.3.5 + with: + server: ${{ secrets.FTP_SERVER }} + username: ${{ secrets.FTP_USERNAME }} + password: ${{ secrets.FTP_PASSWORD }} + protocol: ftps + local-dir: ./my-app/dist/ + server-dir: ./ + dry-run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run }} + + # Never wipe first. The panel lives in admin/ under this same root, + # and a clean slate would take it and its config.php with it. + dangerous-clean-slate: false + + exclude: | + **/.git* + **/.git*/** + **/.DS_Store + .ftp-deploy-sync-state.json + photos/README.md + + - name: Deploy the admin panel to /admin + uses: SamKirkland/FTP-Deploy-Action@v4.3.5 + with: + server: ${{ secrets.FTP_SERVER }} + username: ${{ secrets.FTP_USERNAME }} + password: ${{ secrets.FTP_PASSWORD }} + protocol: ftps + local-dir: ./admin/ + server-dir: ./admin/ + dry-run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run }} + + # deploy-admin.yml refuses to write anywhere it cannot already find + # config.php, which is the right rule for finding a panel that exists + # somewhere unknown -- it is what stopped three deploys landing in + # folders nobody served. It cannot create a panel in a new place, + # though, and that is what this step is for: admin/ sits directly + # under a login directory we have now positively identified as the + # web root, so there is no unknown left for that rule to guard. + # + # config.php is still excluded and still has to be put there by hand + # once. It holds the database password, it is gitignored, and it is + # the one file no deploy has ever written -- which is exactly why it + # can be trusted as the panel's fingerprint. + dangerous-clean-slate: false + + # State is kept per-folder. Sharing one file with the site deploy + # above would make each run think the other's files had vanished. + state-name: .ftp-deploy-admin-state.json + + # config.php is excluded, and this deploy no longer generates one. + # + # It used to: the file was written from DB_* secrets and uploaded + # beside the panel. That was reasonable when the panel's config could + # only live there, and it is now actively harmful. The panel reads + # nitesha-config/config.php from above the document root precisely so + # that a deploy cannot reach it -- but config_path() checks beside + # the panel FIRST, so an uploaded copy silently wins over the real + # one. A stale secret would then take the admin offline with + # "Database unavailable" on a deploy that only meant to change the + # website, and the working config would still be sitting there + # untouched and ignored. + exclude: | + **/.git* + **/.git*/** + **/.DS_Store + config.php + .ftp-deploy-admin-state.json diff --git a/.github/workflows/deploy-my-app.yml b/.github/workflows/deploy-my-app.yml new file mode 100644 index 00000000000..84b94b3272c --- /dev/null +++ b/.github/workflows/deploy-my-app.yml @@ -0,0 +1,58 @@ +name: Deploy my-app + +on: + push: + branches: [main] + paths: + - 'my-app/**' + - '.github/workflows/deploy-my-app.yml' + workflow_dispatch: + +# Allow GITHUB_TOKEN to publish to Pages. +permissions: + contents: read + pages: write + id-token: write + +# Let an in-flight deploy finish; queue at most one more. +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + defaults: + run: + working-directory: my-app + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: my-app/package-lock.json + + - name: Install + run: npm ci + + - name: Lint + run: npm run lint + + - name: Build + run: npm run build + + - uses: actions/upload-pages-artifact@v3 + with: + path: my-app/dist + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/e2e-base.yml b/.github/workflows/e2e-base.yml deleted file mode 100644 index d79a43b8676..00000000000 --- a/.github/workflows/e2e-base.yml +++ /dev/null @@ -1,35 +0,0 @@ -on: - workflow_call: - inputs: - testScript: - required: true - type: string - -name: E2E - -jobs: - test: - name: 'Test (${{ matrix.os }}, Node ${{ matrix.node }})' - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: - - 'ubuntu-latest' - node: - - '16' - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node }} - cache: 'npm' - - name: Install - run: npm ci --prefer-offline - - name: Initialize Global Git config - run: | - git config --global core.autocrlf false - git config --global user.name "Create React App" - git config --global user.email "cra@email.com" - - name: Run tests - run: ${{ inputs.testScript }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 47bd841572e..00000000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Lint - -on: [push, pull_request] - -jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: '16' - cache: 'npm' - - name: Install - run: npm ci --prefer-offline - - name: Alex - run: npm run alex - - name: Prettier - run: npm run prettier -- --list-different - - name: Eslint - run: npm run eslint -- --max-warnings 0 diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml new file mode 100644 index 00000000000..1915876caf6 --- /dev/null +++ b/.github/workflows/node.js.yml @@ -0,0 +1,44 @@ +# CI for my-app, the app this repo actively maintains. +# +# This previously ran npm ci / build / test at the repo root. That could not +# pass: the root package.json describes a create-react-app app with no src/ or +# public/ directory, so react-scripts build always failed looking for +# public/index.html. +name: Node.js CI + +on: + push: + branches: ['main'] + pull_request: + branches: ['main'] + +jobs: + build: + runs-on: ubuntu-latest + defaults: + run: + working-directory: my-app + + strategy: + fail-fast: false + matrix: + # Vite 8 requires ^20.19.0 || >=22.12.0, so 18.x is not supported. + node-version: [20.x, 22.x] + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: npm + cache-dependency-path: my-app/package-lock.json + + - name: Install + run: npm ci + + - name: Lint + run: npm run lint + + - name: Build + run: npm run build diff --git a/.github/workflows/site-is-current.yml b/.github/workflows/site-is-current.yml new file mode 100644 index 00000000000..8065cd31117 --- /dev/null +++ b/.github/workflows/site-is-current.yml @@ -0,0 +1,69 @@ +# Fails when the site committed at the top level is not the current build. +# +# Hostinger's Git deployment clones this repository into the document root and +# runs no build, so whatever sits at the top level IS the website. A change to +# my-app/ or admin/ that does not also update those files never reaches the +# server -- and nothing reports a problem. The deploy succeeds, the site keeps +# serving the previous build, and the only symptom is that the thing you +# changed is not there. +# +# This makes that failure loud. It builds, republishes, and fails if that +# changed anything -- which means the committed site was stale. +# +# It writes nothing back and pushes nothing. The fix is a person running +# `npm run publish:site` and committing, because a workflow that could commit +# the fix would also be a workflow that can push to main unattended, which is a +# far larger permission than catching a stale folder is worth. +name: Site is current + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22.x + cache: npm + cache-dependency-path: my-app/package-lock.json + + - name: Install + working-directory: my-app + run: npm ci + + - name: Build + working-directory: my-app + run: npm run build + + - name: Republish and see whether anything moved + run: | + node scripts/publish-site.mjs + + if git diff --quiet; then + echo "The committed site matches the build." + exit 0 + fi + + echo "::error::The site committed at the top level is not the current build, so these changes will not reach the website." + echo "" + echo "Hostinger clones this repository into the document root and runs no" + echo "build, so the site keeps serving whatever is committed here." + echo "" + echo "Fix it with:" + echo "" + echo " npm run publish:site" + echo " git add -A && git commit" + echo "" + echo "Out of date:" + git diff --name-only | sed 's/^/ /' + exit 1 diff --git a/.gitignore b/.gitignore index 3ae88f84d8b..75337af51bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,27 @@ -.idea/ -.vscode/ -node_modules/ -build/ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc .DS_Store -*.tgz -my-app* -template/src/__tests__/__snapshots__/ -lerna-debug.log +.env.local +.env.development.local +.env.test.local +.env.production.local + npm-debug.log* yarn-debug.log* yarn-error.log* -/.changelog -.npm/ + +# Live admin panel config. Holds real database credentials and must never be +# committed; config.sample.php is the template to copy on a new server. +admin/config.php diff --git a/.htaccess b/.htaccess new file mode 100644 index 00000000000..02c2f192fab --- /dev/null +++ b/.htaccess @@ -0,0 +1,199 @@ +# Apache configuration for niteshacars.in on Hostinger. +# +# This file only matters once the domain points at Hostinger rather than +# GitHub Pages. Pages ignores it; Apache reads it on every request. +# +# It has three jobs: keep everyone on HTTPS, keep /admin out of the way of the +# website's routing, and serve a real 404 for a URL that does not exist. + +# ----------------------------------------------------------------- routing -- + + RewriteEngine On + + # HTTPS. Hostinger terminates TLS upstream, so %{HTTPS} is not reliable here + # -- the X-Forwarded-Proto header is what actually says how the visitor + # arrived. Both are checked, because a setup where only %{HTTPS} is set would + # otherwise redirect forever. + RewriteCond %{HTTP:X-Forwarded-Proto} =http [OR] + RewriteCond %{HTTPS} !=on + RewriteCond %{HTTP:X-Forwarded-Proto} !=https + RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L] + + # /admin is the PHP panel. Hand it straight to PHP and stop: without this, + # the website's 404 rule below would swallow every panel URL that is not a + # file on disk, and the panel would answer every page with the website's + # "page not found". + RewriteCond %{REQUEST_URI} ^/admin(/|$) [NC] + RewriteRule ^ - [L] + + # ------------------------------------------------- where the pictures live -- + # + # Every photograph on this site is stored above the document root, so Apache + # cannot serve it and a small PHP reader in the panel has to. That used to + # show in the address: + # + # /admin/photos/maruti-swift-self-drive-nagercoil-9f3a1c4b7d2e5a60.jpg + # + # The words were right and the folder was wrong. Two things follow from + # "admin" being in the URL of every picture on a site that sells car hire: + # + # 1. It is a dead word in the one part of an image's address Google reads + # besides the file name. /car-photos/ is not. + # + # 2. It is one robots.txt edit from disaster. Disallowing /admin/ is the + # normal, obvious thing to do -- any SEO tool will suggest it -- and + # doing it would make every photograph on the site uncrawlable at a + # stroke. robots.txt carries three Allow lines to prevent exactly that, + # and a rule that survives only because nobody tidies it is not a rule. + # + # Served from the site root instead, which is where a visitor would assume + # they live anyway. The /admin/ addresses still work -- they are what is + # stored in pages already indexed -- so this adds an address, it does not + # move one. + # The static pictures were renamed to say what they show rather than where + # they sit on the page. Google Images may hold the old addresses, and a + # renamed file is a 404 to anything that already knew the old one -- so the + # old names redirect rather than simply stopping. 301, because the move is + # permanent and that is what passes the old address's standing to the new. + RewriteRule ^hero-car\.webp$ /self-drive-car-rental-nitesha-cars-and-bikes.webp [R=301,L] + RewriteRule ^open-road\.webp$ /mountain-road-at-sunrise-self-drive-car-rental.webp [R=301,L] + RewriteRule ^car-brezza\.avif$ /maruti-brezza-self-drive-car-rental-nagercoil.avif [R=301,L] + RewriteRule ^photos/car-brezza\.avif$ /photos/car-brezza--maruti-brezza-self-drive-rental-nagercoil.avif [R=301,L] + + RewriteRule ^car-photos/([A-Za-z0-9._-]+)$ /admin/photo.php?f=$1 [L] + RewriteRule ^site-images/([A-Za-z0-9._-]+)$ /admin/brand.php?f=$1 [L] + RewriteRule ^place-photos/([A-Za-z0-9._-]+)$ /admin/place-photo.php?f=$1 [L] + + # The site's pages are real directories with real index.html files -- the + # build writes one per route so each carries its own title and description. + # Apache serves those itself, so there is nothing to rewrite for a URL that + # exists. Anything left over genuinely does not exist, and gets 404.html + # with a 404 status rather than a copy of the home page with a 200: a soft + # 404 tells Google the page is real and gets the dud URL indexed. + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule ^ /404.html [L] + + +ErrorDocument 404 /404.html + +# ------------------------------------------------------------- compression -- +# +# Every page is real HTML now rather than an empty
, which is +# what made the site readable before its JavaScript arrives -- and it made the +# home page 74 KB instead of 3 KB. Compressed it is about 12 KB, so this is +# most of that cost handed back, on the one file that has to arrive before +# anything at all can be drawn. +# +# Brotli first where the host has it: it beats gzip by roughly 15% on markup +# and every browser this site sees supports it. gzip is the fallback, and one +# of the two is on every Apache install worth deploying to. +# +# Images and fonts are left alone deliberately. WebP, AVIF, JPEG and WOFF2 are +# already compressed; running them through again spends CPU on every request to +# make the file very slightly larger. + + AddOutputFilterByType BROTLI_COMPRESS text/html text/plain text/xml text/css + AddOutputFilterByType BROTLI_COMPRESS application/javascript application/json + AddOutputFilterByType BROTLI_COMPRESS image/svg+xml application/xml + + + + AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css + AddOutputFilterByType DEFLATE application/javascript application/json + AddOutputFilterByType DEFLATE image/svg+xml application/xml + # Proxies must not hand a compressed copy to a client that cannot read it. + + Header append Vary Accept-Encoding + + + +# ----------------------------------------------------------------- caching -- + + ExpiresActive On + + # Build assets carry a content hash in the filename, so a changed file is a + # changed URL and these can be cached hard. + + ExpiresDefault "access plus 1 year" + Header set Cache-Control "public, max-age=31536000, immutable" + + + # Photographs keep their names across edits, so a year would mean a visitor + # who has seen the old one never gets the new one. A week is long enough to + # help and short enough to fix a mistake. + + ExpiresDefault "access plus 1 week" + + + # HTML is the index of everything else, so it is always revalidated. Cache + # it and a deploy would not reach anyone who had visited before. + + ExpiresDefault "access plus 0 seconds" + Header set Cache-Control "no-cache, must-revalidate" + + + +# ---------------------------------------------------------------- security -- + + # Stops a browser from second-guessing a file's declared type, which is how + # an uploaded image gets treated as a script. + Header set X-Content-Type-Options "nosniff" + Header set Referrer-Policy "strict-origin-when-cross-origin" + # Nothing here needs a camera, a microphone or a location. + Header set Permissions-Policy "camera=(), microphone=(), geolocation=()" + + +# Dotfiles are configuration, not content. .htaccess itself is already +# protected by Apache, but this covers anything else that lands here. + + Require all denied + + +# FilesMatch above matches file NAMES, which is not enough for a directory: +# a request for /.git/config has a filename of "config" and sails straight +# past it. This blocks the paths instead. +# +# A served .git is not a tidiness problem -- standard tooling reconstructs the +# entire repository from it, and this repository's history contains a database +# password from before it was moved out. +# +# This is no longer a precaution. Hostinger's Git deployment clones the whole +# repository into the document root, so .git is there on every deploy by +# design, and cannot be removed without giving up automatic deploys. These +# rules are what stands between it and the internet, which is also why the +# password still needs rotating in hPanel rather than being considered handled. + + RewriteEngine On + RewriteRule (^|/)\.(git|svn|hg|env|ssh)(/|$) - [F,L] + + # .github is not caught above: the pattern needs a slash or the end of the + # path straight after "git", and this has "hub" there instead. + RewriteRule (^|/)\.github(/|$) - [F,L] + + # The rest of the repository, which now sits in the web root beside the + # site. FilesMatch cannot express these because it matches one filename at a + # time, and what needs blocking here is whole trees. + RewriteRule ^(my-app|scripts|assets-original|marketing)(/|$) - [F,L] + + +# Source and build files are not content either. None of these should ever +# reach a web root, but naming them costs nothing and a stray upload is +# exactly how they arrive. + + Require all denied + + +# Documentation, which the deploy now puts in the web root: README.md, +# CODE_OF_CONDUCT.md, LICENSE, and the note in photos/ describing how to name a +# car photograph. None of it is secret and none of it is for visitors. + + Require all denied + + + Require all denied + + +# The directory listing is off by default on most hosts, but not all, and an +# open listing of the web root is a map of everything on the server. +Options -Indexes diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 18a58d393ef..00000000000 --- a/.prettierignore +++ /dev/null @@ -1,9 +0,0 @@ -build/ -package-lock.json -test/fixtures/webpack-message-formatting/src/AppBabel.js -test/fixtures/webpack-message-formatting/src/AppCss.css -packages/react-error-overlay/fixtures/bundle* -packages/react-error-overlay/fixtures/inline* -packages/react-error-overlay/fixtures/junk* -packages/react-error-overlay/lib/ -packages/react-error-overlay/coverage/ diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 7a7fd85ee2a..00000000000 --- a/.prettierrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "arrowParens": "avoid", - "singleQuote": true, - "semi": true -} diff --git a/.site-manifest.json b/.site-manifest.json new file mode 100644 index 00000000000..16af8a0736d --- /dev/null +++ b/.site-manifest.json @@ -0,0 +1,38 @@ +[ + ".htaccess", + ".vite", + "404.html", + "CNAME", + "about", + "apple-touch-icon.png", + "assets", + "bikes", + "blog", + "build.json", + "car-rental", + "cars", + "contact", + "favicon.svg", + "fleet-check.html", + "fonts", + "index.html", + "maruti-brezza-self-drive-car-rental-nagercoil.avif", + "monthly", + "mountain-road-at-sunrise-self-drive-car-rental-1280w.webp", + "mountain-road-at-sunrise-self-drive-car-rental-800w.webp", + "mountain-road-at-sunrise-self-drive-car-rental.webp", + "nitesha-cars-and-bikes-emblem.webp", + "nitesha-cars-and-bikes-logo-nagercoil-v2-400w.webp", + "nitesha-cars-and-bikes-logo-nagercoil-v2-800w.webp", + "nitesha-cars-and-bikes-logo-nagercoil-v2.webp", + "nri", + "photos", + "places", + "robots.txt", + "self-drive-car-rental-nitesha-cars-and-bikes.webp", + "services", + "sitemap.xml", + "tariff", + "tourist-vehicles", + "wedding-cars" +] diff --git a/.vite/manifest.json b/.vite/manifest.json new file mode 100644 index 00000000000..00196d14db6 --- /dev/null +++ b/.vite/manifest.json @@ -0,0 +1,252 @@ +{ + "_PageHeader-B6dZ6efH.js": { + "file": "assets/PageHeader-B6dZ6efH.js", + "name": "PageHeader", + "imports": [ + "_SectionArt-CRGDPXOj.js" + ] + }, + "_SectionArt-CRGDPXOj.js": { + "file": "assets/SectionArt-CRGDPXOj.js", + "name": "SectionArt" + }, + "_ServicePage-2R3tb8Gb.js": { + "file": "assets/ServicePage-2R3tb8Gb.js", + "name": "ServicePage", + "imports": [ + "_SectionArt-CRGDPXOj.js", + "index.html", + "_PageHeader-B6dZ6efH.js" + ] + }, + "index.html": { + "file": "assets/index-BchjlLBy.js", + "name": "index", + "src": "index.html", + "isEntry": true, + "imports": [ + "_SectionArt-CRGDPXOj.js" + ], + "dynamicImports": [ + "src/pages/About.tsx", + "src/pages/Cars.tsx", + "src/pages/Bikes.tsx", + "src/pages/WeddingCars.tsx", + "src/pages/TouristVehicles.tsx", + "src/pages/Nri.tsx", + "src/pages/Monthly.tsx", + "src/pages/Tariff.tsx", + "src/pages/Blog.tsx", + "src/pages/Contact.tsx", + "src/pages/Places.tsx", + "src/pages/Services.tsx", + "src/pages/NotFound.tsx", + "src/pages/CarRentalAreas.tsx", + "src/pages/Town.tsx", + "src/pages/CarModel.tsx", + "src/pages/ServiceArea.tsx", + "src/pages/Post.tsx" + ], + "css": [ + "assets/index-CTc6RczQ.css" + ] + }, + "src/pages/About.tsx": { + "file": "assets/About-BagSiI9V.js", + "name": "About", + "src": "src/pages/About.tsx", + "isDynamicEntry": true, + "imports": [ + "_SectionArt-CRGDPXOj.js", + "index.html", + "_PageHeader-B6dZ6efH.js" + ] + }, + "src/pages/Bikes.tsx": { + "file": "assets/Bikes-BGuFlx-H.js", + "name": "Bikes", + "src": "src/pages/Bikes.tsx", + "isDynamicEntry": true, + "imports": [ + "_SectionArt-CRGDPXOj.js", + "_ServicePage-2R3tb8Gb.js" + ] + }, + "src/pages/Blog.tsx": { + "file": "assets/Blog-C8E-F237.js", + "name": "Blog", + "src": "src/pages/Blog.tsx", + "isDynamicEntry": true, + "imports": [ + "_SectionArt-CRGDPXOj.js", + "index.html", + "_PageHeader-B6dZ6efH.js" + ] + }, + "src/pages/CarModel.tsx": { + "file": "assets/CarModel-BNr5p2Tt.js", + "name": "CarModel", + "src": "src/pages/CarModel.tsx", + "isDynamicEntry": true, + "imports": [ + "_SectionArt-CRGDPXOj.js", + "index.html", + "_PageHeader-B6dZ6efH.js", + "src/pages/NotFound.tsx" + ] + }, + "src/pages/CarRentalAreas.tsx": { + "file": "assets/CarRentalAreas-BLwzjYsU.js", + "name": "CarRentalAreas", + "src": "src/pages/CarRentalAreas.tsx", + "isDynamicEntry": true, + "imports": [ + "_SectionArt-CRGDPXOj.js", + "index.html", + "_PageHeader-B6dZ6efH.js" + ] + }, + "src/pages/Cars.tsx": { + "file": "assets/Cars-BT-gZgK7.js", + "name": "Cars", + "src": "src/pages/Cars.tsx", + "isDynamicEntry": true, + "imports": [ + "_SectionArt-CRGDPXOj.js", + "index.html", + "_PageHeader-B6dZ6efH.js" + ] + }, + "src/pages/Contact.tsx": { + "file": "assets/Contact-sMllwbKq.js", + "name": "Contact", + "src": "src/pages/Contact.tsx", + "isDynamicEntry": true, + "imports": [ + "_SectionArt-CRGDPXOj.js", + "index.html", + "_PageHeader-B6dZ6efH.js" + ] + }, + "src/pages/Monthly.tsx": { + "file": "assets/Monthly-DUOyZQBa.js", + "name": "Monthly", + "src": "src/pages/Monthly.tsx", + "isDynamicEntry": true, + "imports": [ + "_SectionArt-CRGDPXOj.js", + "index.html", + "_PageHeader-B6dZ6efH.js" + ] + }, + "src/pages/NotFound.tsx": { + "file": "assets/NotFound-BOhuBiIT.js", + "name": "NotFound", + "src": "src/pages/NotFound.tsx", + "isDynamicEntry": true, + "imports": [ + "_SectionArt-CRGDPXOj.js", + "_PageHeader-B6dZ6efH.js" + ] + }, + "src/pages/Nri.tsx": { + "file": "assets/Nri-CYBhMv_I.js", + "name": "Nri", + "src": "src/pages/Nri.tsx", + "isDynamicEntry": true, + "imports": [ + "_SectionArt-CRGDPXOj.js", + "index.html", + "_PageHeader-B6dZ6efH.js" + ] + }, + "src/pages/Places.tsx": { + "file": "assets/Places-DeqB0d8-.js", + "name": "Places", + "src": "src/pages/Places.tsx", + "isDynamicEntry": true, + "imports": [ + "_SectionArt-CRGDPXOj.js", + "index.html", + "_PageHeader-B6dZ6efH.js" + ] + }, + "src/pages/Post.tsx": { + "file": "assets/Post-MIeUtpwm.js", + "name": "Post", + "src": "src/pages/Post.tsx", + "isDynamicEntry": true, + "imports": [ + "_SectionArt-CRGDPXOj.js", + "index.html", + "_PageHeader-B6dZ6efH.js", + "src/pages/NotFound.tsx" + ] + }, + "src/pages/ServiceArea.tsx": { + "file": "assets/ServiceArea-ir_N5Ogd.js", + "name": "ServiceArea", + "src": "src/pages/ServiceArea.tsx", + "isDynamicEntry": true, + "imports": [ + "_SectionArt-CRGDPXOj.js", + "index.html", + "_PageHeader-B6dZ6efH.js", + "src/pages/NotFound.tsx" + ] + }, + "src/pages/Services.tsx": { + "file": "assets/Services-BNXO2tQW.js", + "name": "Services", + "src": "src/pages/Services.tsx", + "isDynamicEntry": true, + "imports": [ + "_SectionArt-CRGDPXOj.js", + "index.html", + "_PageHeader-B6dZ6efH.js" + ] + }, + "src/pages/Tariff.tsx": { + "file": "assets/Tariff-DM63uEOD.js", + "name": "Tariff", + "src": "src/pages/Tariff.tsx", + "isDynamicEntry": true, + "imports": [ + "_SectionArt-CRGDPXOj.js", + "index.html", + "_PageHeader-B6dZ6efH.js" + ] + }, + "src/pages/TouristVehicles.tsx": { + "file": "assets/TouristVehicles-Or946-7L.js", + "name": "TouristVehicles", + "src": "src/pages/TouristVehicles.tsx", + "isDynamicEntry": true, + "imports": [ + "_SectionArt-CRGDPXOj.js", + "_ServicePage-2R3tb8Gb.js" + ] + }, + "src/pages/Town.tsx": { + "file": "assets/Town-BnsId0Rm.js", + "name": "Town", + "src": "src/pages/Town.tsx", + "isDynamicEntry": true, + "imports": [ + "_SectionArt-CRGDPXOj.js", + "index.html", + "_PageHeader-B6dZ6efH.js", + "src/pages/NotFound.tsx" + ] + }, + "src/pages/WeddingCars.tsx": { + "file": "assets/WeddingCars-DEx5ukzM.js", + "name": "WeddingCars", + "src": "src/pages/WeddingCars.tsx", + "isDynamicEntry": true, + "imports": [ + "_SectionArt-CRGDPXOj.js", + "_ServicePage-2R3tb8Gb.js" + ] + } +} \ No newline at end of file diff --git a/404.html b/404.html new file mode 100644 index 00000000000..6f51f2763cc --- /dev/null +++ b/404.html @@ -0,0 +1,89 @@ + + + + + + + + + + + + Self Drive Car Rental in Nagercoil & Kanyakumari + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Self-drive rental car in Nagercoil, Kanyakumari district

Nagercoil & Kanyakumari district

Self drive car & bike rental in Nagercoiland across Kanyakumari district

Take the wheel. We'll handle the rest.

Self drive cars, bike rental, wedding cars and tourist vehicles with a driver — across Nagercoil, Marthandam, Colachel and the whole of Kanyakumari district. Transparent rates and a deposit you get back.

Check a date

Tell us when you need it and we will call you back.

WhatsApp
  • Delivered to your doorstep
  • Zero hidden charges
  • Day, week or month

Why hire from us

Straight answers, and a vehicle that turns up

Hiring a vehicle should not be the complicated part of a trip. We tell you the rate, the limits and the deposit before you take the car, and we bring it to you wherever you are in the district.

Every vehicle carries three numbers before you take it: the daily rate, the kilometres included each day, and what anything beyond that costs. The deposit is stated too, and it comes back when the car does. Nothing is discovered at the end.

Booking, start to finish

How it works

  1. Pick your car

    Browse the fleet and choose what suits the trip — a hatchback for the city, an SUV for the highway.

  2. Send an enquiry

    Tell us your dates. We'll call back to confirm the car is free and agree the rate.

  3. Collect and drive

    Bring a valid licence and ID. Pay the deposit, take the keys, and the car is yours.

  4. Bring it back

    Drop it back at the time agreed. We check it over, and the deposit comes back the same day.

Deposits are refundable. It is returned after the car comes back, less any extra-KM charges or damage. KM limits and extra-KM rates are listed on every car above, so there is nothing to discover later.

Where we deliver

Where we hire across Kanyakumari district

We serve the whole of Kanyakumari district in Tamil Nadu. Tell us where you are and we will sort out pickup.

Somewhere not listed? Ask us — if it is in the district, we can usually help. See every town we deliver to.

Where people go

The coast, the temples and the waterfalls are all an easy drive from Nagercoil. Take the car and go at your own pace.

View all 12 places

Coast

Vattakottai Fort

A small granite fort right on the shore about six kilometres up the coast from Kanyakumari, with the sea on one side and the hills behind. Half an hour is enough, and the drive there along the coast road is half the reason to go.

Directionsto Vattakottai Fort, opens Google Maps in a new tab

Heritage

Padmanabhapuram Palace

The old wooden palace of the Travancore kings at Thuckalay, and the largest of its kind anywhere. Bigger inside than it looks and worth ninety minutes. It shuts earlier in the afternoon than most people expect, so check before setting off.

Directionsto Padmanabhapuram Palace, opens Google Maps in a new tab

Questions we are asked most

If yours is not here, call or send a WhatsApp message and we will answer it straight.

Do I need a deposit to take a self drive car?

Yes, a refundable security deposit, and you get it back when the vehicle comes back in the same condition. The amount depends on the vehicle and we tell you the figure before you book, not when you arrive.

What documents do I need to rent a car without a driver?

Your original driving licence and one photo ID. We take a copy at handover. For visitors from abroad, bring your passport and an international driving permit alongside your home licence.

Is there a kilometre limit, and what happens if I go over it?

Every vehicle comes with a daily kilometre allowance, and anything beyond it is charged at a fixed rate per kilometre. Both numbers are on the tariff page and on your booking, so there is nothing to work out afterwards.

Can you deliver the car to Nagercoil railway station or the airport?

We deliver and collect across Kanyakumari district — Nagercoil, Kanyakumari, Marthandam, Colachel, Thuckalay and the towns around them — including the railway station and the bus stand. Tell us where you land and when, and we will say what we can do.

Can I take the car into Kerala?

Usually yes — Trivandrum is an hour away and a lot of our hires cross the border. Tell us where you are going when you book, so the paperwork is right before you set off.

Do you rent two-wheelers as well as cars?

Yes. Scooters and motorcycles by the hour, the day or the week, on the same terms as the cars. A two-wheeler is the easier way around Nagercoil and out to the coast.

Is a monthly rental cheaper than paying by the day?

Considerably. Weekly, fortnightly and monthly rates all bring the daily figure down, which is what most people on a work posting or a long stay take. Ask us for a monthly quote with the kilometres you expect to drive.

Do you provide a car with a driver for sightseeing?

Yes. Tourist vehicles come with a driver who knows the district, for Kanyakumari, Padmanabhapuram Palace, the temples and the beaches, by the day or for a full itinerary.

How early should a wedding car be booked?

As early as you can in the wedding season. Decorated cars and the premium vehicles go first, and the dates everybody wants are the ones that run out.

How do I book?

Send the dates through the form on this site or on WhatsApp, and we call you back to confirm what is free. Nothing is paid online — the deposit and the rental are settled when you take the vehicle.

Need a car for your dates?

Tell us when and what you need. We will confirm what is free and what it costs.

+ + diff --git a/CHANGELOG-0.x.md b/CHANGELOG-0.x.md deleted file mode 100644 index 5463a61a4e9..00000000000 --- a/CHANGELOG-0.x.md +++ /dev/null @@ -1,1435 +0,0 @@ -## 1.0.0 and Newer Versions - -**Please refer to [CHANGELOG-1.x.md](./CHANGELOG-1.x.md) for the 1.x range, and [CHANGELOG.md](CHANGELOG.md) for the newer versions.** - -## 0.9.5 (March 9, 2017) - -#### :bug: Bug Fix - -- `react-scripts` - - - [#1783](https://github.com/facebook/create-react-app/pull/1783) **Work around Node 7.7.2 bug that crashes `npm start`.** ([@ryanwalters](https://github.com/ryanwalters)) - -#### :nail_care: Enhancement - -- `eslint-config-react-app` - - - [#1773](https://github.com/facebook/create-react-app/pull/1773) Remove `guard-for-in` lint rule. ([@spicyj](https://github.com/spicyj)) - -- `react-scripts` - - [#1760](https://github.com/facebook/create-react-app/pull/1760) Suggest `serve` for running in production. ([@leo](https://github.com/leo)) - - [#1747](https://github.com/facebook/create-react-app/pull/1747) Display `yarn` instead of `yarnpkg` when creating a new app. ([@lpalmes](https://github.com/lpalmes)) - -#### :memo: Documentation - -- `react-scripts` - - - [#1756](https://github.com/facebook/create-react-app/pull/1756) Add Yarn steps for adding Flow. ([@zertosh](https://github.com/zertosh)) - -#### :house: Internal - -- `babel-preset-react-app` - - - [#1742](https://github.com/facebook/create-react-app/pull/1742) Switch to `babel-preset-env` to remove the deprecation warning. ([@Timer](https://github.com/Timer)) - -#### Committers: 6 - -- Andres Suarez ([zertosh](https://github.com/zertosh)) -- Ben Alpert ([spicyj](https://github.com/spicyj)) -- Joe Haddad ([Timer](https://github.com/Timer)) -- Leo Lamprecht ([leo](https://github.com/leo)) -- Lorenzo Palmes ([lpalmes](https://github.com/lpalmes)) -- Ryan Walters ([ryanwalters](https://github.com/ryanwalters)) - -### Migrating from 0.9.4 to 0.9.5 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@0.9.5 -``` - -## 0.9.4 (March 6, 2017) - -#### :bug: Bug Fix - -- `create-react-app` - - - [#1706](https://github.com/facebook/create-react-app/pull/1706) Extract compressed package for package name. ([@Timer](https://github.com/Timer)) - - You may now specify a scoped package for `--scripts-version` and obtain a working installation. - - - [#1695](https://github.com/facebook/create-react-app/pull/1695) Print why installation was aborted. ([@tgig](https://github.com/tgig)) - -- `react-scripts` - - - [#1727](https://github.com/facebook/create-react-app/pull/1727) Fix ejecting from a scoped fork. ([@gaearon](https://github.com/gaearon)) - - Ejecting now works within a scoped fork. - - - [#1721](https://github.com/facebook/create-react-app/pull/1721) Fix hot reloading for WebpackDevServer after eject. ([@gaearon](https://github.com/gaearon)) - -- `react-dev-utils` - - - [#1690](https://github.com/facebook/create-react-app/pull/1690) Fix `openBrowser()` when `BROWSER=open` on macOS. ([@bpierre](https://github.com/bpierre)) - - - [#1696](https://github.com/facebook/create-react-app/pull/1696) Improve reliability of port detection. ([@chrisdrackett](https://github.com/chrisdrackett)) - -#### :nail_care: Enhancement - -- `eslint-config-react-app`, `react-scripts` - - - [#1705](https://github.com/facebook/create-react-app/pull/1705) Add support for `ignoreRestSiblings` in `no-unused-vars`. ([@chrisdrackett](https://github.com/chrisdrackett)) - - Linter no longer warns when using rest properties to remove variables from an object. - - - [#1542](https://github.com/facebook/create-react-app/pull/1542) Bump `jsx-a11y` version. ([@bondz](https://github.com/bondz)) - -- `react-dev-utils`, `react-scripts` - - - [#1726](https://github.com/facebook/create-react-app/pull/1726) Extract generic build functions into `react-dev-utils`. ([@viankakrisna](https://github.com/viankakrisna)) - -- Other - - - [#1402](https://github.com/facebook/create-react-app/pull/1402) Stub `package.json` for e2e test. ([@matoilic](https://github.com/matoilic)) - -#### :memo: Documentation - -- `react-scripts` - - [#1710](https://github.com/facebook/create-react-app/pull/1710) Update now.sh deployment instructions. ([@replaid](https://github.com/replaid)) - - [#1717](https://github.com/facebook/create-react-app/pull/1717) Add docs for Apache client side routing. ([@viankakrisna](https://github.com/viankakrisna)) - - [#1698](https://github.com/facebook/create-react-app/pull/1698) Suggest to use `.env` for enabling polling mode. ([@gaearon](https://github.com/gaearon)) - - [#1687](https://github.com/facebook/create-react-app/pull/1687) Fixed missing `--recursive` flag in first `npm run watch-css` command. ([@mklemme](https://github.com/mklemme)) - -#### :house: Internal - -- `react-scripts` - - [#1736](https://github.com/facebook/create-react-app/pull/1736) Fix eject for linked react-scripts. ([@tuchk4](https://github.com/tuchk4)) - - [#1741](https://github.com/facebook/create-react-app/pull/1741) Fix internal linting setup. ([@gaearon](https://github.com/gaearon)) - - [#1730](https://github.com/facebook/create-react-app/pull/1730) Fix Node 4 e2e tests. ([@Timer](https://github.com/Timer)) -- `eslint-config-react-app` - - [#1740](https://github.com/facebook/create-react-app/pull/1740) Relax ESLint config peerDependency. ([@gaearon](https://github.com/gaearon)) -- `eslint-config-react-app`, `react-dev-utils`, `react-scripts` - - [#1729](https://github.com/facebook/create-react-app/pull/1729) Lint internal scripts with `eslint:recommended`. ([@gaearon](https://github.com/gaearon)) -- `react-dev-utils` - - [#1724](https://github.com/facebook/create-react-app/pull/1724) Don't use ES6 in a file that should run on Node 4. ([@gaearon](https://github.com/gaearon)) -- Other - - [#1723](https://github.com/facebook/create-react-app/pull/1723) Skip AppVeyor CI builds for Markdown changes. ([@gaearon](https://github.com/gaearon)) - - [#1707](https://github.com/facebook/create-react-app/pull/1707) Add double quotes to escape spaces in paths in e2e. ([@viankakrisna](https://github.com/viankakrisna)) - - [#1688](https://github.com/facebook/create-react-app/pull/1688) Upgrade `lerna` version. ([@viankakrisna](https://github.com/viankakrisna)) - -#### Committers: 11 - -- Ade Viankakrisna Fadlil ([viankakrisna](https://github.com/viankakrisna)) -- Bond ([bondz](https://github.com/bondz)) -- Chris Drackett ([chrisdrackett](https://github.com/chrisdrackett)) -- Dan Abramov ([gaearon](https://github.com/gaearon)) -- Joe Haddad ([Timer](https://github.com/Timer)) -- Mato Ilic ([matoilic](https://github.com/matoilic)) -- Myk Klemme ([mklemme](https://github.com/mklemme)) -- Pierre Bertet ([bpierre](https://github.com/bpierre)) -- Ryan Platte ([replaid](https://github.com/replaid)) -- Travis Giggy ([tgig](https://github.com/tgig)) -- Valerii Sorokobatko ([tuchk4](https://github.com/tuchk4)) - -### Migrating from 0.9.3 to 0.9.4 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@0.9.4 -``` - -You may also optionally update the global command-line utility for scoped package support: - -```sh -npm install -g create-react-app@1.3.0 -``` - -## 0.9.3 (February 28, 2017) - -#### :rocket: New Feature - -- `create-react-app` - - - [#1423](https://github.com/facebook/create-react-app/pull/1423) **Fall back to Yarn offline cache when creating a new project.** ([@voxsim](https://github.com/voxsim)) - - If you are using Yarn, and you have created at least one app previously, Create React App now works offline. - - Yarn offline installation demo - -#### :bug: Bug Fix - -- `react-scripts` - - - [#1665](https://github.com/facebook/create-react-app/pull/1665) Temporarily disable ESLint caching because of a bug. ([@gaearon](https://github.com/gaearon)) - -- `create-react-app` - - [#1675](https://github.com/facebook/create-react-app/pull/1675) Delete project folder on failed installation on Windows. ([@johann-sonntagbauer](https://github.com/johann-sonntagbauer)) - - [#1662](https://github.com/facebook/create-react-app/pull/1662) Validate project name before creating a project. ([@johann-sonntagbauer](https://github.com/johann-sonntagbauer)) - - [#1669](https://github.com/facebook/create-react-app/pull/1669) Make sure React dependencies aren’t pinned in new projects. ([@johann-sonntagbauer](https://github.com/johann-sonntagbauer)) - -#### :nail_care: Enhancement - -- `react-scripts` - - - [#1677](https://github.com/facebook/create-react-app/pull/1677) Add `X-FORWARDED` headers for proxy requests. ([@johann-sonntagbauer](https://github.com/johann-sonntagbauer)) - -#### :memo: Documentation - -- `react-scripts` - - - [#1657](https://github.com/facebook/create-react-app/pull/1657) Tweak the Visual Studio Code debugging guide. ([@ryansully](https://github.com/ryansully)) - -#### :house: Internal - -- End-to-end Tests - - - [#1648](https://github.com/facebook/create-react-app/pull/1648) Add Windows CI tests for better stability. ([@Timer](https://github.com/Timer)) - -#### Committers: 5 - -- Dan Abramov ([gaearon](https://github.com/gaearon)) -- Joe Haddad ([Timer](https://github.com/Timer)) -- Johann Hubert Sonntagbauer ([johann-sonntagbauer](https://github.com/johann-sonntagbauer)) -- Ryan Sullivan ([ryansully](https://github.com/ryansully)) -- Simon Vocella ([voxsim](https://github.com/voxsim)) - -### Migrating from 0.9.2 to 0.9.3 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@0.9.3 -``` - -You may also optionally update the global command-line utility for offline Yarn cache support: - -```sh -npm install -g create-react-app@1.2.1 -``` - -## 0.9.2 (February 26, 2017) - -#### :nail_care: Enhancement - -- `create-react-app` - - - [#1253](https://github.com/facebook/create-react-app/pull/1253) **Install time optimization.** ([@n3tr](https://github.com/n3tr)) - - React, ReactDOM, and `react-scripts` are now installed in the same install instead of two different installs. This reduces app creation time by a noticeable amount. - - - [#1512](https://github.com/facebook/create-react-app/pull/1512) **Graceful error handling.** ([@chitchu](https://github.com/chitchu)) - - If an error occurs while `create-react-app` is running, it will now clean up and not leave a broken project to reduce confusion. - - - [#1193](https://github.com/facebook/create-react-app/pull/1193) Suggest upgrading to NPM >= 3 for faster install times. ([@mobinni](https://github.com/mobinni)) - - - [#1603](https://github.com/facebook/create-react-app/pull/1603) Allow app creation in a WebStorm project. ([@driquelme](https://github.com/driquelme)) - - - [#1570](https://github.com/facebook/create-react-app/pull/1570) Allow git urls in `--scripts-version`. ([@tomconroy](https://github.com/tomconroy)) - -- `react-scripts` - - - [#1578](https://github.com/facebook/create-react-app/pull/1578) Enable lint caching in development. ([@viankakrisna](https://github.com/viankakrisna)) - - - [#1478](https://github.com/facebook/create-react-app/pull/1478) Update the build script message to show the correct port. ([@chyipin](https://github.com/chyipin)) - - - [#1567](https://github.com/facebook/create-react-app/pull/1567) Remove .bin files after eject. ([@tuchk4](https://github.com/tuchk4)) - - - [#1560](https://github.com/facebook/create-react-app/pull/1560) Bump `recursive-readdir`. ([@wtgtybhertgeghgtwtg](https://github.com/wtgtybhertgeghgtwtg)) - -#### :bug: Bug Fix - -- `react-scripts` - - - [#1635](https://github.com/facebook/create-react-app/pull/1635) **Fix Jest configuration.** ([@Timer](https://github.com/Timer)) - - Fixes ejecting on Windows for macOS and Linux machines. - - - [#1356](https://github.com/facebook/create-react-app/pull/1356) Fix workflow if react-scripts package is linked via npm-link. ([@tuchk4](https://github.com/tuchk4)) - - Advanced users may opt to fork `react-scripts` instead of ejecting so they still receive upstream updates.
- `react-scripts` will now function as expected when linking to a development version.
- Previously, you could not test changes with an existing application via linking. - - - [#1585](https://github.com/facebook/create-react-app/pull/1585) Ensure PORT environment variable is an integer. ([@matoilic](https://github.com/matoilic)) - - - [#1628](https://github.com/facebook/create-react-app/pull/1628) Show correct port for pushstate-server URL text. ([@mattccrampton](https://github.com/mattccrampton)) - - - [#1647](https://github.com/facebook/create-react-app/pull/1647) Fix `npm test` on Windows ([@gaearon](https://github.com/gaearon)) - -#### :memo: Documentation - -- User Guides - - [#1391](https://github.com/facebook/create-react-app/pull/1391) Add note how to resolve missing required files for Heroku. ([@sbritoig](https://github.com/sbritoig)) - - [#1577](https://github.com/facebook/create-react-app/pull/1577) Add a how-to on `react-snapshot`. ([@superhighfives](https://github.com/superhighfives)) - - [#1121](https://github.com/facebook/create-react-app/pull/1121) Add documentation for customizing Bootstrap theme. ([@myappincome](https://github.com/myappincome)) - - [#1540](https://github.com/facebook/create-react-app/pull/1540) Document debugging in Visual Studio Code. ([@bondz](https://github.com/bondz)) - - [#1618](https://github.com/facebook/create-react-app/pull/1618) Add note about when to import Bootstrap CSS. ([@joewoodhouse](https://github.com/joewoodhouse)) - - [#1518](https://github.com/facebook/create-react-app/pull/1518) Update flow configuration documentation. ([@SBrown52](https://github.com/SBrown52)) - - [#1625](https://github.com/facebook/create-react-app/pull/1625) Specify that NODE_ENV is set to 'production' during the build step. ([@mderazon](https://github.com/mderazon)) - - [#1573](https://github.com/facebook/create-react-app/pull/1573) Update Jest documentation links. ([@mkermani144](https://github.com/mkermani144)) - - [#1564](https://github.com/facebook/create-react-app/pull/1564) Add --recursive to Sass watch script. ([@aleburato](https://github.com/aleburato)) - - [#1561](https://github.com/facebook/create-react-app/pull/1561) Use https in link in documentation. ([@dariocravero](https://github.com/dariocravero)) - - [#1562](https://github.com/facebook/create-react-app/pull/1562) Update `jest-enzyme` documentation. ([@kiranps](https://github.com/kiranps)) - - [#1543](https://github.com/facebook/create-react-app/pull/1543) Update CSS preprocessor instructions. ([@aleburato](https://github.com/aleburato)) - - [#1338](https://github.com/facebook/create-react-app/pull/1338) Add link to Azure deployment tutorial. ([@tpetrina](https://github.com/tpetrina)) - - [#1320](https://github.com/facebook/create-react-app/pull/1320) Document how to disable autoprefix feature. ([@rrubas](https://github.com/rrubas)) - - [#1313](https://github.com/facebook/create-react-app/pull/1313) List features beyond ES6 supported by create-react-app. ([@jonathanconway](https://github.com/jonathanconway)) - - [#1008](https://github.com/facebook/create-react-app/pull/1008) Add Saas support documentation. ([@tsironis](https://github.com/tsironis)) - - [#994](https://github.com/facebook/create-react-app/pull/994) Suggest `jest-enzyme` for simplifying test matchers. ([@blainekasten](https://github.com/blainekasten)) - - [#1608](https://github.com/facebook/create-react-app/pull/1608) Add note for using CHOKIDAR_USEPOLLING in virtual machines to enable HMR. ([@AJamesPhillips](https://github.com/AJamesPhillips)) - - [#1495](https://github.com/facebook/create-react-app/pull/1495) Add useful link to react-scripts. ([@pd4d10](https://github.com/pd4d10)) -- READMEs - - [#1576](https://github.com/facebook/create-react-app/pull/1576) Switch from Neo to Neutrino. ([@eliperelman](https://github.com/eliperelman)) - - [#1275](https://github.com/facebook/create-react-app/pull/1275) Suggest yarn commands in addition to npm. ([@lifez](https://github.com/lifez)) - -#### :house: Internal - -- `babel-preset-react-app` - - [#1598](https://github.com/facebook/create-react-app/pull/1598) Remove redundant babel-plugin-transform-es2015-parameters. ([@christophehurpeau](https://github.com/christophehurpeau)) -- Other - - [#1534](https://github.com/facebook/create-react-app/pull/1534) Use yarn@latest in e2e. ([@gaearon](https://github.com/gaearon)) - - [#1295](https://github.com/facebook/create-react-app/pull/1295) Make node version check more robust in e2e. ([@pugnascotia](https://github.com/pugnascotia)) - - [#1503](https://github.com/facebook/create-react-app/pull/1503) Fix `test -e` in e2e. ([@igetgames](https://github.com/igetgames)) - -#### Committers: 36 - -- Ade Viankakrisna Fadlil ([viankakrisna](https://github.com/viankakrisna)) -- Alessandro Burato ([aleburato](https://github.com/aleburato)) -- Alexander James Phillips ([AJamesPhillips](https://github.com/AJamesPhillips)) -- Blaine Kasten ([blainekasten](https://github.com/blainekasten)) -- Bond ([bondz](https://github.com/bondz)) -- Charlie Gleason ([superhighfives](https://github.com/superhighfives)) -- Christophe Hurpeau ([christophehurpeau](https://github.com/christophehurpeau)) -- Dan Abramov ([gaearon](https://github.com/gaearon)) -- Daniel Riquelme ([driquelme](https://github.com/driquelme)) -- Darío Javier Cravero ([dariocravero](https://github.com/dariocravero)) -- Dimitris Tsironis ([tsironis](https://github.com/tsironis)) -- Eli Perelman ([eliperelman](https://github.com/eliperelman)) -- Jirat Ki. ([n3tr](https://github.com/n3tr)) -- Joe Haddad ([Timer](https://github.com/Timer)) -- Joe Woodhouse ([joewoodhouse](https://github.com/joewoodhouse)) -- Jonathan Conway ([jonathanconway](https://github.com/jonathanconway)) -- Marcus R. Brown ([igetgames](https://github.com/igetgames)) -- Mato Ilic ([matoilic](https://github.com/matoilic)) -- Matt Crampton ([mattccrampton](https://github.com/mattccrampton)) -- Michael DeRazon ([mderazon](https://github.com/mderazon)) -- Mo Binni ([mobinni](https://github.com/mobinni)) -- Mohammad Kermani ([mkermani144](https://github.com/mkermani144)) -- Phawin Khongkhasawan ([lifez](https://github.com/lifez)) -- Roman Rubas ([rrubas](https://github.com/rrubas)) -- Rory Hunter ([pugnascotia](https://github.com/pugnascotia)) -- Tom Conroy ([tomconroy](https://github.com/tomconroy)) -- Toni Petrina ([tpetrina](https://github.com/tpetrina)) -- Valerii Sorokobatko ([tuchk4](https://github.com/tuchk4)) -- Vicente Jr Yuchitcho ([chitchu](https://github.com/chitchu)) -- [SBrown52](https://github.com/SBrown52) -- [chyipin](https://github.com/chyipin) -- [myappincome](https://github.com/myappincome) -- [sbritoig](https://github.com/sbritoig) -- [wtgtybhertgeghgtwtg](https://github.com/wtgtybhertgeghgtwtg) -- kiran ps ([kiranps](https://github.com/kiranps)) -- pd4d10 ([pd4d10](https://github.com/pd4d10)) - -### Migrating from 0.9.0 to 0.9.2 - -**Note:** 0.9.1 had known issues so you should skip it. - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@0.9.2 -``` - -You may also optionally update the global command-line utility for more efficient installs (thanks [@n3tr](https://github.com/n3tr)): - -```sh -npm install -g create-react-app@1.1.0 -``` - -## 0.9.1 (February 25, 2017) - -This release has known issues and you should skip it. Update directly to 0.9.2 instead. - -## 0.9.0 (February 11, 2017) - -Thanks to [@Timer](https://github.com/timer) for cutting this release. - -#### :rocket: New Feature - -- `react-scripts` - - - [#1489](https://github.com/facebook/create-react-app/pull/1489) Support setting `"homepage"` to `"."` to generate relative asset paths. ([@tibdex](https://github.com/tibdex)) - - Applications that don’t use the HTML5 `pushState` API can now be built to be served from any relative URL. To enable this, specify `"."` as your `homepage` setting in `package.json`. It used to be possible before with a few known bugs, but they should be fixed now. See [Serving the Same Build from Different Paths](https://github.com/facebook/create-react-app/blob/main/packages/react-scripts/template/README.md#serving-the-same-build-from-different-paths). - - - [#937](https://github.com/facebook/create-react-app/pull/1504) Add `PUBLIC_URL` environment variable for advanced use. ([@EnoahNetzach](https://github.com/EnoahNetzach)) - - If you use a CDN to serve the app, you can now specify `PUBLIC_URL` environment variable to override the base URL (including the hostname) for resources referenced from the built code. This new variable is mentioned in the new [Advanced Configuration](https://github.com/facebook/create-react-app/blob/main/packages/react-scripts/template/README.md#advanced-configuration) section. - - - [#1440](https://github.com/facebook/create-react-app/pull/1440) Make all `REACT_APP_*` environment variables accessible in `index.html`. ([@jihchi](https://github.com/jihchi)) - - This makes all environment variables previously available in JS, also available in the HTML file, for example `%REACT_APP_MY_VARIABLE%`. See [Referencing Environment Variables in HTML](https://github.com/facebook/create-react-app/blob/main/packages/react-scripts/template/README.md#referencing-environment-variables-in-the-html). - -- `react-dev-utils` - - - [#1148](https://github.com/facebook/create-react-app/pull/1148) Configure which browser to open with `npm start`. ([@GAumala](https://github.com/GAumala)) - - You can now disable the automatic browser launching by setting the `BROWSER` environment variable to `none`. You can also specify a different browser (or an arbitrary script) to open by default, [as supported by `opn` command](https://github.com/sindresorhus/opn#app) that we use under the hood. See [Advanced Configuration](https://github.com/facebook/create-react-app/blob/main/packages/react-scripts/template/README.md#advanced-configuration). - -#### :boom: Breaking Change - -- `react-scripts` - - - [#1522](https://github.com/facebook/create-react-app/pull/1522) Upgrade dependencies. ([@Timer](https://github.com/Timer)) - - [#1432](https://github.com/facebook/create-react-app/pull/1432) Bump Jest version. ([@gaearon](https://github.com/gaearon)) - - [#1311](https://github.com/facebook/create-react-app/pull/1311) Updated `babel-jest` and `jest` packages to 18.0.0. ([@lopezator](https://github.com/lopezator)) - - Jest has been updated to 18 and has introduced some [breaking changes and new features](https://facebook.github.io/jest/blog/2016/12/15/2016-in-jest.html). - -- `react-scripts`, `react-dev-utils` - - - [#1264](https://github.com/facebook/create-react-app/pull/1264) Remove interactive shell check when opening browser on start. ([@CaryLandholt](https://github.com/CaryLandholt)) - - Non-interactive terminals no longer automatically disable launching of the browser. Instead, you need to [specify `none` as `BROWSER` environment variable](https://github.com/facebook/create-react-app/blob/main/packages/react-scripts/template/README.md#advanced-configuration) if you wish to disable it. - -#### :bug: Bug Fix - -- `react-scripts` - - - [#1441](https://github.com/facebook/create-react-app/pull/1441) Added `babel-runtime` dependency to deduplicate dependencies when using Yarn. ([@jkimbo](https://github.com/jkimbo)) - - This works around a bug in Yarn that caused newly created projects to be over 400MB. Now they are down to 126MB, just like with npm 3. - - - [#1522](https://github.com/facebook/create-react-app/pull/1522) Upgrade dependencies. ([@Timer](https://github.com/Timer)) - - [#1458](https://github.com/facebook/create-react-app/pull/1458) Additionally remove `react-scripts` from dependencies on eject. ([@creynders](https://github.com/creynders)) - - [#1309](https://github.com/facebook/create-react-app/pull/1309) Bump `babel-loader` version (#1009). ([@frontsideair](https://github.com/frontsideair)) - - [#1267](https://github.com/facebook/create-react-app/pull/1267) Only gitignore directories in root, not deep. ([@jayphelps](https://github.com/jayphelps)) - -- `react-dev-utils` - - - [#1377](https://github.com/facebook/create-react-app/pull/1377) webpack-dev-server patch for 'still-ok' success status. ([@TheBlackBolt](https://github.com/TheBlackBolt)) - - [#1274](https://github.com/facebook/create-react-app/pull/1274) Downgrading to compatible version of SockJS-Client. ([@holloway](https://github.com/holloway)) - - [#1247](https://github.com/facebook/create-react-app/pull/1247) Only open Chrome tab if BROWSER is missing or is Chrome. ([@gaearon](https://github.com/gaearon)) - -#### :nail_care: Enhancement - -- `react-scripts` - - - [#1496](https://github.com/facebook/create-react-app/pull/1496) Make build exit with error code when interrupted. ([@brandones](https://github.com/brandones)) - - [#1352](https://github.com/facebook/create-react-app/pull/1352) More descriptive error message for `env.CI = true` warnings causing failures. ([@jayphelps](https://github.com/jayphelps)) - - [#1264](https://github.com/facebook/create-react-app/pull/1264) Remove interactive shell check when opening browser on start. ([@CaryLandholt](https://github.com/CaryLandholt)) - - [#1311](https://github.com/facebook/create-react-app/pull/1311) Updated `babel-jest` and `jest` packages to 18.0.0. ([@lopezator](https://github.com/lopezator)) - - [#1432](https://github.com/facebook/create-react-app/pull/1432) Bump Jest version. ([@gaearon](https://github.com/gaearon)) - - [#1507](https://github.com/facebook/create-react-app/pull/1507) fix: add yarn gitignores. ([@adjohnson916](https://github.com/adjohnson916)) - - [#1510](https://github.com/facebook/create-react-app/pull/1510) Add missing `'\n'` to the end of `package.json` file. ([@pd4d10](https://github.com/pd4d10)) - - [#1324](https://github.com/facebook/create-react-app/pull/1324) Use npm script hooks to avoid `&&` in deploy script. ([@zpao](https://github.com/zpao)) - -- `create-react-app` - - - [#1270](https://github.com/facebook/create-react-app/pull/1270) gh-1269: Enabling nested folder paths for project name. ([@dinukadesilva](https://github.com/dinukadesilva)) - -#### :memo: Documentation - -- User Guide - - - [#1515](https://github.com/facebook/create-react-app/pull/1515) readme: Advanced Configuration. ([@Timer](https://github.com/Timer)) - - [#1513](https://github.com/facebook/create-react-app/pull/1513) clarifying the use of custom environment variables. ([@calweb](https://github.com/calweb)) - - [#1511](https://github.com/facebook/create-react-app/pull/1511) Change "OS X" references to "macOS". ([@RodrigoHahn](https://github.com/RodrigoHahn)) - - [#1482](https://github.com/facebook/create-react-app/pull/1482) Edit User Guide: Add ESLint config for VS Code users. ([@vulong23](https://github.com/vulong23)) - - [#1483](https://github.com/facebook/create-react-app/pull/1483) Reflect websocket proxy support on README (#1013). ([@frontsideair](https://github.com/frontsideair)) - - [#1453](https://github.com/facebook/create-react-app/pull/1453) Readme: Removes experimental from Jest snapshot. ([@frehner](https://github.com/frehner)) - - [#1437](https://github.com/facebook/create-react-app/pull/1437) Added links to tutorials for integrating cra with an api backend. ([@alexdriaguine](https://github.com/alexdriaguine)) - - [#1422](https://github.com/facebook/create-react-app/pull/1422) Add causes of dev server not detecting changes. ([@jetpackpony](https://github.com/jetpackpony)) - - [#1260](https://github.com/facebook/create-react-app/pull/1260) Heroku Deployment: Adds a note on how to resolve "File/Module Not Found Errors" . ([@MsUzoAgu](https://github.com/MsUzoAgu)) - - [#1256](https://github.com/facebook/create-react-app/pull/1256) Add "Changing the Page Title" to User Guide. ([@gaearon](https://github.com/gaearon)) - - [#1245](https://github.com/facebook/create-react-app/pull/1245) Replace the Flow documentation section. ([@gaearon](https://github.com/gaearon)) - - [#1514](https://github.com/facebook/create-react-app/pull/1514) corrected minor typo. ([@crowchirp](https://github.com/crowchirp)) - - [#1393](https://github.com/facebook/create-react-app/pull/1393) replace two space syntax with br tag. ([@carlsagan21](https://github.com/carlsagan21)) - - [#1384](https://github.com/facebook/create-react-app/pull/1384) Document Flow support. ([@dschep](https://github.com/dschep)) - -- READMEs - - - [#1375](https://github.com/facebook/create-react-app/pull/1375) Change console.log for errors and warnings. ([@jimmyhmiller](https://github.com/jimmyhmiller)) - - [#1369](https://github.com/facebook/create-react-app/pull/1369) Add missing import in react-dev-utils README.md. ([@pedronauck](https://github.com/pedronauck)) - -#### :house: Internal - -- Internal Test Suite - - - [#1519](https://github.com/facebook/create-react-app/pull/1519) Add test cases for PUBLIC_URL and relative path. ([@Timer](https://github.com/Timer)) - - [#1484](https://github.com/facebook/create-react-app/pull/1484) Improve e2e-kitchensink and Jest coverage. ([@Timer](https://github.com/Timer)) - - [#1463](https://github.com/facebook/create-react-app/pull/1463) Minor code style and wrong expect. ([@tuchk4](https://github.com/tuchk4)) - - [#1470](https://github.com/facebook/create-react-app/pull/1470) E2e jsdom fix. ([@EnoahNetzach](https://github.com/EnoahNetzach)) - - [#1187](https://github.com/facebook/create-react-app/pull/1187) Use a more sophisticated template for end-to-end testing.. ([@EnoahNetzach](https://github.com/EnoahNetzach)) - -- Other - - - [#1289](https://github.com/facebook/create-react-app/pull/1289) Remove path-exists from dependencies and replace it with fs.existsSync. ([@halfzebra](https://github.com/halfzebra)) - -#### Committers: 35 - -- Alex Driaguine ([alexdriaguine](https://github.com/alexdriaguine)) -- Anders D. Johnson ([adjohnson916](https://github.com/adjohnson916)) -- Anthony F. ([frehner](https://github.com/frehner)) -- Brandon Istenes ([brandones](https://github.com/brandones)) -- Calvin Webster ([calweb](https://github.com/calweb)) -- Cary Landholt ([CaryLandholt](https://github.com/CaryLandholt)) -- Chandan Rai ([crowchirp](https://github.com/crowchirp)) -- Christian Raidl ([Chris-R3](https://github.com/Chris-R3)) -- Dan Abramov ([gaearon](https://github.com/gaearon)) -- Daniel Schep ([dschep](https://github.com/dschep)) -- David ([lopezator](https://github.com/lopezator)) -- Dinuka De Silva ([dinukadesilva](https://github.com/dinukadesilva)) -- Eduard Kyvenko ([halfzebra](https://github.com/halfzebra)) -- Fabrizio Castellarin ([EnoahNetzach](https://github.com/EnoahNetzach)) -- Fatih ([frontsideair](https://github.com/frontsideair)) -- Gabriel Aumala ([GAumala](https://github.com/GAumala)) -- Jay Phelps ([jayphelps](https://github.com/jayphelps)) -- Jih-Chi Lee ([jihchi](https://github.com/jihchi)) -- Jimmy Miller ([jimmyhmiller](https://github.com/jimmyhmiller)) -- Joe Haddad ([Timer](https://github.com/Timer)) -- Johnny Magrippis ([jmagrippis](https://github.com/jmagrippis)) -- Jonathan Kim ([jkimbo](https://github.com/jkimbo)) -- MUA ([MsUzoAgu](https://github.com/MsUzoAgu)) -- Matthew Holloway ([holloway](https://github.com/holloway)) -- Nguyen Le Vu Long ([vulong23](https://github.com/vulong23)) -- Paul O’Shannessy ([zpao](https://github.com/zpao)) -- Pedro Nauck ([pedronauck](https://github.com/pedronauck)) -- Robbie H ([TheBlackBolt](https://github.com/TheBlackBolt)) -- Thibault Derousseaux ([tibdex](https://github.com/tibdex)) -- Valerii ([tuchk4](https://github.com/tuchk4)) -- Vasiliy Taranov ([jetpackpony](https://github.com/jetpackpony)) -- [RodrigoHahn](https://github.com/RodrigoHahn) -- creynders ([creynders](https://github.com/creynders)) -- pd4d10 ([pd4d10](https://github.com/pd4d10)) -- soo ([carlsagan21](https://github.com/carlsagan21)) - -### Migrating from 0.8.5 to 0.9.0 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@0.9.0 -``` - -Then, run your tests. If you are affected by breaking changes from Jest 18, consult [blog post](https://facebook.github.io/jest/blog/2016/12/15/2016-in-jest.html), [changelog](https://github.com/facebook/jest/blob/master/CHANGELOG.md#jest-1800), and [documentation](https://facebook.github.io/jest/docs/getting-started.html). You might need to update any snapshots since their format might have changed. - -If you relied on the browser not starting in non-interactive terminals, you now need to explicitly specify `BROWSER=none` as an environment variable to disable it. - -## 0.8.5 (January 9, 2017) - -Thanks to [@fson](https://github.com/fson) for cutting this release. - -#### :bug: Bug Fix - -- `create-react-app`, `react-scripts` - - - [#1365](https://github.com/facebook/create-react-app/pull/1365) Use yarnpkg alias to run Yarn. ([@fson](https://github.com/fson)) - - Fixes an issue where running `create-react-app` failed on systems with Apache Hadoop installed because it falsely detected Hadoop YARN executable as Yarn package manager. - -#### Committers: 1 - -- Ville Immonen ([fson](https://github.com/fson)) - -### Migrating from 0.8.4 to 0.8.5 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@0.8.5 -``` - -You may also optionally update the global command-line utility: - -```sh -npm install -g create-react-app@1.0.3 -``` - -## 0.8.4 (December 11, 2016) - -#### :bug: Bug Fix - -- `react-scripts` - - - [#1233](https://github.com/facebook/create-react-app/pull/1233) Disable subresource integrity temporarily. ([@Timer](https://github.com/Timer)) - - We added [Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) checks to the build output in 0.8.2 but it turns out that they may fail in browsers using special compression proxies, such as Chrome on Android, when served over HTTP. We disabled the checks until we can find a safe way to add them. - -- `react-dev-utils` - - - [#1226](https://github.com/facebook/create-react-app/pull/1226) Fix weird lint output. ([@n3tr](https://github.com/n3tr)) - - Fixes strange lint message formatting in some edge cases. - - - [#1215](https://github.com/facebook/create-react-app/pull/1215) Fix - openChrome won't open default browser (using Canary). ([@n3tr](https://github.com/n3tr)) - - Fixes a regression that caused stable Google Chrome to be opened even if you are using Canary as the default browser. - -- `create-react-app` - - - [#1223](https://github.com/facebook/create-react-app/pull/1223) Clean up Yarn detection and install code. ([@fson](https://github.com/fson)) - - Fixes noisy output on Windows when Yarn is not installed. - - - [#1224](https://github.com/facebook/create-react-app/pull/1224) Exit with an error code when npm/yarn install fails. ([@fson](https://github.com/fson)) - -#### :nail_care: Enhancement - -- `react-scripts` - - - [#1237](https://github.com/facebook/create-react-app/pull/1237) Clear scrollback in test mode. ([@gaearon](https://github.com/gaearon)) - - Ensures test watcher clears the console before running. - - - [#1229](https://github.com/facebook/create-react-app/pull/1229) Disable jest watch mode when --coverage flag is present [#1207]. ([@BenoitAverty](https://github.com/BenoitAverty)) - - Since coverage doesn't work well with watch mode, we don’t run the watcher on `npm test -- --coverage` anymore. - - - [#1212](https://github.com/facebook/create-react-app/pull/1212) Proxy rewrites Origin header to match the target server URL. ([@koles](https://github.com/koles)) - - Makes sure more API endpoints can work with the `proxy` setting. - - - [#1222](https://github.com/facebook/create-react-app/pull/1222) Disable gh-page setup instruction if scripts.deploy has been added. ([@n3tr](https://github.com/n3tr)) - - Suppresses the instructions printed at the end of `npm run build` if `npm run deploy` already exists. - -- `create-react-app` - - - [#1236](https://github.com/facebook/create-react-app/pull/1236) Tweak console messages. ([@gaearon](https://github.com/gaearon)) - - Makes error messages more friendly. - - - [#1195](https://github.com/facebook/create-react-app/pull/1195) Use "commander" for cli argv handling. ([@EnoahNetzach](https://github.com/EnoahNetzach)) - - Adds `create-react-app --help` with a list of options. - -- `react-dev-utils` - - - [#1211](https://github.com/facebook/create-react-app/pull/1211) Use a better clear console sequence. ([@gaearon](https://github.com/gaearon)) - - Ensures the development server clears the terminal when files are changed. - -#### :memo: Documentation - -- `react-dev-utils` - - - [#1232](https://github.com/facebook/create-react-app/pull/1232) [documentation] fix html-dev-plugin link in react-dev-utils doc. ([@shogunsea](https://github.com/shogunsea)) - -- `react-scripts` - - - [#1220](https://github.com/facebook/create-react-app/pull/1220) Adding troubleshooting information about Subresource Integrity digests.. ([@dfbaskin](https://github.com/dfbaskin)) - -#### :house: Internal - -- `react-scripts` - - - [#1214](https://github.com/facebook/create-react-app/pull/1214) Bump babel-eslint version. ([@existentialism](https://github.com/existentialism)) - -#### Committers: 10 - -- Benoit Averty ([BenoitAverty](https://github.com/BenoitAverty)) -- Brian Ng ([existentialism](https://github.com/existentialism)) -- Dan Abramov ([gaearon](https://github.com/gaearon)) -- Dave Baskin ([dfbaskin](https://github.com/dfbaskin)) -- Fabrizio Castellarin ([EnoahNetzach](https://github.com/EnoahNetzach)) -- Jirat Ki. ([n3tr](https://github.com/n3tr)) -- Joe Haddad ([Timer](https://github.com/Timer)) -- Pavel Kolesnikov ([koles](https://github.com/koles)) -- Shogun Sea ([shogunsea](https://github.com/shogunsea)) -- Ville Immonen ([fson](https://github.com/fson)) - -### Migrating from 0.8.3 to 0.8.4 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@0.8.4 -``` - -You may also optionally update the global command-line utility: - -```sh -npm install -g create-react-app@1.0.2 -``` - -## 0.8.3 (December 8, 2016) - -#### :bug: Bug Fix - -- `create-react-app` - - - [#1204](https://github.com/facebook/create-react-app/pull/1204) Catch synchronous errors from spawning yarn. ([@gaearon](https://github.com/gaearon)) - - Fixes a crash when running `create-react-app` in some cases. - -- `react-scripts` - - - [#1203](https://github.com/facebook/create-react-app/pull/1203) Update webpack-subresource-integrity to fix Windows builds. ([@gaearon](https://github.com/gaearon)) - - Fixes a crash when running `npm run build` on Windows. - - - [#1201](https://github.com/facebook/create-react-app/pull/1201) Instruct Jest to load native components from RNW instead of RN. ([@remon-georgy](https://github.com/remon-georgy)) - - Fixes tests for users of React Native Web. - -#### :memo: Documentation - -- `react-scripts` - - - [#806](https://github.com/facebook/create-react-app/pull/806) Add syntax highlighting configuration guide. ([@mareksuscak](https://github.com/mareksuscak)) - -#### Committers: 3 - -- Dan Abramov ([gaearon](https://github.com/gaearon)) -- Marek Suscak ([mareksuscak](https://github.com/mareksuscak)) -- Remon Georgy ([remon-georgy](https://github.com/remon-georgy)) - -### Migrating from 0.8.2 to 0.8.3 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@0.8.3 -``` - -You can optionally update the global CLI too: - -```sh -npm install -g create-react-app@1.0.1 -``` - -## 0.8.2 (December 7, 2016) - -#### :rocket: New Feature - -- `react-scripts` - - - [#1176](https://github.com/facebook/create-react-app/pull/1176) Add Subresource Integrity support. ([@XVincentX](https://github.com/XVincentX)) - - The generated HTML now includes [Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) attributes ensuring that your users aren't served malicious code if your CDN gets compromised. - -#### :bug: Bug Fix - -- `react-scripts` - - - [#1197](https://github.com/facebook/create-react-app/pull/1197) Let Jest handle all file types. ([@gaearon](https://github.com/gaearon)) - - Since 0.8.0, we started treating imports of any unknown file extensions as URLs. However, we had to revert this change for the test configuration in 0.8.1 because of a bug causing false positives. In 0.8.2, we are fixing this and making test configuration treat imports with unknown extensions the same way as we do in the browser environment. - - - [#1194](https://github.com/facebook/create-react-app/pull/1194) Only honor relative `NODE_PATH`. ([@gaearon](https://github.com/gaearon)) - - Historically we have allowed specifying `NODE_PATH` environment variable as a way to allow “absolute imports”. For example, running `NODE_PATH=src npm start` in Bash or `set NODE_PATH=src&&npm start` in Windows Cmd would let you import anything inside `src` without specifying a relative path. However, we found a few nasty edge cases when Node.js core modules end up being in `NODE_PATH` and erroneously become bundled. As a result the build would crash on some systems when some libraries are imported. To fix this, we now only honor relative paths from `NODE_PATH` in Create React App. This means the existing use case for absolute imports is still supported (`src` in the example above is relative), but absolute paths in `NODE_PATH` (such as paths to Node.js core modules) will be ignored. - - - [#1188](https://github.com/facebook/create-react-app/pull/1188) Update webpack to fix source map issues. ([@gaearon](https://github.com/gaearon)) - - Since 0.8.0, we show source maps in development instead of the compiled code. However, it has come to our attention that webpack's source map implementation had issues interpreting Babel output, and caused source maps to be wrong and breakpoints to be unusable in some cases. webpack has released a fix for this, and we have updated the minimal version of webpack that we are using. - - - [#1180](https://github.com/facebook/create-react-app/pull/1180) Use `file-loader` for svgs. ([@bogdansoare](https://github.com/bogdansoare)) - - Since 0.8.0, we are treating all imports with non-JS/CSS extensions the same way. Importing them gives you a string with their URL, and if their content is small enough (less than 10K), the URL is in fact an inlined [data URI](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs). However, this doesn't work well with SVGs in case you use them for a sprite system since fragments don't work in data URIs, and it's wasteful to inline the same sprite SVG many times. To fix this, we have added an exception so that SVG files never get inlined. - -- `react-dev-utils` - - - [#1165](https://github.com/facebook/create-react-app/pull/1165) Chrome 'open tab' reuse an empty tab when possible. ([@n3tr](https://github.com/n3tr)) - - Fixes an issue that caused two tabs to get opened instead of just one. It also fixes some cases where the window with the existing tab would not get activated. - -- `babel-preset-react-app` - - - [#1179](https://github.com/facebook/create-react-app/pull/1179) Fix Babel issues in tests by applying the right transforms. ([@gaearon](https://github.com/gaearon)) - - Fixes regressions in test environment that caused syntax errors with generators and `async` / `await`. - -#### :nail_care: Enhancement - -- `eslint-config-react-app` - - - [#1191](https://github.com/facebook/create-react-app/pull/1191) Relax peerDependencies for ESLint preset. ([@gaearon](https://github.com/gaearon)) - - This allows the preset to be used in more apps without peer dependency conflicts. We still pin the exact versions in apps that haven't ejected for extra safety. - - - [#1159](https://github.com/facebook/create-react-app/pull/1159) Make jsx-no-undef rule an error. ([@existentialism](https://github.com/existentialism)) - - Using an undefined type in JSX is now treated as a hard lint error because it is guaranteed to crash application at runtime. - -- `react-scripts` - - - [#1175](https://github.com/facebook/create-react-app/pull/1175) Remove path module from webpack config on eject. ([@harunhasdal](https://github.com/harunhasdal)) - - This makes the output after ejecting a bit cleaner. - - - [#1120](https://github.com/facebook/create-react-app/pull/1120) Add `testURL` to Jest config. ([@spudly](https://github.com/spudly)) - - This fixes an error when running tests that interact with History API in jsdom. - -#### :memo: Documentation - -- `react-scripts` - - [#1143](https://github.com/facebook/create-react-app/pull/1143) Add deploy to Firebase CDN on template's README (Closes [#374](https://github.com/facebook/create-react-app/issues/374)). ([@guilhermebruzzi](https://github.com/guilhermebruzzi)) - - [#1099](https://github.com/facebook/create-react-app/pull/1099) Fix minor typo/grammar. ([@alex-wilmer](https://github.com/alex-wilmer)) - - [#1168](https://github.com/facebook/create-react-app/pull/1168) Add "npm run build silently fails" to Troubleshooting. ([@gaearon](https://github.com/gaearon)) - -#### Committers: 12 - -- Alex Wilmer ([alex-wilmer](https://github.com/alex-wilmer)) -- Bogdan Soare ([bogdansoare](https://github.com/bogdansoare)) -- Brian Ng ([existentialism](https://github.com/existentialism)) -- Dan Abramov ([gaearon](https://github.com/gaearon)) -- Fabrizio Castellarin ([EnoahNetzach](https://github.com/EnoahNetzach)) -- Guilherme Heynemann Bruzzi ([guilhermebruzzi](https://github.com/guilhermebruzzi)) -- Harun ([harunhasdal](https://github.com/harunhasdal)) -- James Newell ([jameslnewell](https://github.com/jameslnewell)) -- Jirat Ki. ([n3tr](https://github.com/n3tr)) -- Li Xuanji ([zodiac](https://github.com/zodiac)) -- Stephen John Sorensen ([spudly](https://github.com/spudly)) -- Vincenzo Chianese ([XVincentX](https://github.com/XVincentX)) - -### Migrating from 0.8.1 to 0.8.2 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@0.8.2 -``` - -## 0.8.1 (December 4, 2016) - -Thanks to [@fson](https://github.com/fson) for cutting this release. - -#### :bug: Bug Fix - -- `react-scripts` - - [#1149](https://github.com/facebook/create-react-app/pull/1149) Fix incorrectly stubbing JavaScript files with a dot in the import path in tests. ([@fson](https://github.com/fson)) - -### Migrating from 0.8.0 to 0.8.1 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@0.8.1 -``` - -## 0.8.0 (December 3, 2016) - -Thanks to [@fson](https://github.com/fson) for cutting this release. - -#### :rocket: New Feature - -- `react-scripts` - - - [#944](https://github.com/facebook/create-react-app/pull/944) Crash the build during CI whenever linter warnings are encountered. ([@excitement-engineer](https://github.com/excitement-engineer)) - - Linter warnings and errors are now checked during a continuous integration build (set by the `CI` environment variable) and the build will fail if any issues are found. See [Continuous Integration](https://github.com/facebook/create-react-app/blob/main/packages/react-scripts/template/README.md#continuous-integration) for more information. - - - [#1090](https://github.com/facebook/create-react-app/pull/1090) Enable proxying of WebSockets. ([@dceddia](https://github.com/dceddia)) - -- `create-react-app`, `react-scripts` - - - [#898](https://github.com/facebook/create-react-app/pull/898) Support Yarn. ([@fson](https://github.com/fson)) - - Yarn is a new fast, reliable and secure alternative to the `npm` client. If you have Yarn installed, `create-react-app` will use it to install packages when you create an app. It also creates a `yarn.lock` file that should be checked into source control (e.g. git). This ensures the same versions of packages will be installed each time `yarn install` is run, on any machine. - - `react-scripts` now also displays instructions using `yarn` commands for projects using Yarn (projects having a `yarn.lock` file). - - To create a project using Yarn, simply install `yarn` and use `create-react-app` like before: - - ``` - npm install -g yarn create-react-app@latest - - create-react-app my-app # Packages are now installed with Yarn. - ``` - -#### :boom: Breaking Change - -- `babel-preset-react-app` - - - [#902](https://github.com/facebook/create-react-app/pull/902) Enable useBuiltIns option on object-rest-spread. ([@existentialism](https://github.com/existentialism)) - - Object rest spread and JSX now use the native `Object.assign()` method instead of Babel's helper function. If you are using `babel-preset-react-app` directly in your project _and_ targeting browsers that don't have `Object.assign()` available, from now on you need a polyfill for it (e.g. [`object-assign`](https://www.npmjs.com/package/object-assign)). - - **Note:** `react-scripts` already adds this polyfill, so no changes are necessary in Create React App projects. - -#### :bug: Bug Fix - -- `react-scripts` - - - [#978](https://github.com/facebook/create-react-app/pull/978) Move the remove-on-eject-end tag at the end of the file. ([@EnoahNetzach](https://github.com/EnoahNetzach)) - - Fixes a bug in ejected configuration. - - - [#1017](https://github.com/facebook/create-react-app/pull/1017) Don't look for `.babelrc` file during test. ([@nhajidin](https://github.com/nhajidin)) - - Fixes a `.babelrc` file in a parent directory interfering with the `npm test` command. - - - [#951](https://github.com/facebook/create-react-app/pull/951) Check for presence of folders before continuing eject. ([@heldinz](https://github.com/heldinz)) - - Fixes a bug where `eject` failed when a `scripts` or `config` folder already existed in the project. - -- `react-dev-utils` - - - [#1035](https://github.com/facebook/create-react-app/pull/1035) Fix Chrome tab reuse. ([@einarlove](https://github.com/einarlove)) - - Fixes a bug with the app not opening in the existing tab in Chrome. - - - [#964](https://github.com/facebook/create-react-app/pull/964) Catch and noop call to open web browser. ([@spadin](https://github.com/spadin)) - - Not being able to open a browser doesn't crash the development server now. - -- `eslint-config-react-app`, `react-scripts` - - [#953](https://github.com/facebook/create-react-app/pull/953) Fix `.ico` file extension being handled by test configuration. ([@vadzim](https://github.com/vadzim)) - -#### :nail_care: Enhancement - -- `react-scripts` - - - [#1032](https://github.com/facebook/create-react-app/pull/1032) Add support for non-interactive terminal. ([@sheerun](https://github.com/sheerun)) - - [#1078](https://github.com/facebook/create-react-app/pull/1078) Upgrade Jest to 17.0. ([@fson](https://github.com/fson)) - - [#1059](https://github.com/facebook/create-react-app/pull/1059) Use `url-loader` with limit 10k as a default loader. ([@bebbi](https://github.com/bebbi)) - - `react-scripts` now treats imports with any unknown file extension as a resource. Files with a size below 10 KB are inlined using a data URI and larger files copied to the build folder. This removes the need for an internal [whitelist of supported file extensions](https://github.com/facebook/create-react-app/issues/667). Any file that's not JS or CSS is now handled the same way. - - - [#924](https://github.com/facebook/create-react-app/pull/924) Enable JavaScript source maps in development. ([@ekaradon](https://github.com/ekaradon)) - - [#1058](https://github.com/facebook/create-react-app/pull/1058) Add missing dev argument in build script message. ([@nhajidin](https://github.com/nhajidin)) - - [#961](https://github.com/facebook/create-react-app/pull/961) Add `collectCoverageFrom` option to collect coverage on files without any tests. ([@pmackcode](https://github.com/pmackcode)) - - The test script now considers all files in the project when calculating test coverage. - - - [#968](https://github.com/facebook/create-react-app/pull/968) Enable gzip compression in the development server (#966). ([@frontsideair](https://github.com/frontsideair)) - -- `react-dev-utils`, `react-scripts` - - - [#816](https://github.com/facebook/create-react-app/pull/816) add logging of existing default port process on start. ([@ianmcnally](https://github.com/ianmcnally)) - - `react-scripts` can guess which process is running on the port 3000 when it's not available: - - ``` - Something is already running on port 3000. Probably: - my-app - in /Users/ian/dev/my-app - - Would you like to run the app on another port instead? - ``` - -- `react-dev-utils` - - [#963](https://github.com/facebook/create-react-app/pull/963) Allow webpack 2 as a peerDependency in react-dev-utils. ([@einarlove](https://github.com/einarlove)) - -#### :memo: Documentation - -- `react-scripts` - - [#1126](https://github.com/facebook/create-react-app/pull/1126) Add a note about vscode-jest. ([@orta](https://github.com/orta)) - - [#1080](https://github.com/facebook/create-react-app/pull/1080) Add a note for OSX users about watchman and jest. ([@dmr](https://github.com/dmr)) - - [#1071](https://github.com/facebook/create-react-app/pull/1071) Adds to docs - deployment with S3/CloudFront. ([@marcgarreau](https://github.com/marcgarreau)) - - [#976](https://github.com/facebook/create-react-app/pull/976) Added info on using global variables. ([@jhorneman](https://github.com/jhorneman)) - - [#996](https://github.com/facebook/create-react-app/pull/996) Remove redundant `function` from export statement. ([@gnowoel](https://github.com/gnowoel)) - - [#959](https://github.com/facebook/create-react-app/pull/959) Always build before deploying to gh-pages. ([@dsernst](https://github.com/dsernst)) - - [#974](https://github.com/facebook/create-react-app/pull/974) Gently nudge users towards https by default. ([@Swizec](https://github.com/Swizec)) -- Other - - [#1031](https://github.com/facebook/create-react-app/pull/1031) No Configuration -> Convention over Configuration. ([@sheerun](https://github.com/sheerun)) - - [#995](https://github.com/facebook/create-react-app/pull/995) Add Gatsby to alternatives. ([@KyleAMathews](https://github.com/KyleAMathews)) - -#### :house: Internal - -- `react-scripts` - - [#1072](https://github.com/facebook/create-react-app/pull/1072) Replace rimraf with fs-extra functions. ([@existentialism](https://github.com/existentialism)) - - [#1068](https://github.com/facebook/create-react-app/pull/1068) Remove bundledDependencies. ([@fson](https://github.com/fson)) - - [#1057](https://github.com/facebook/create-react-app/pull/1057) Update `css-loader`. ([@nhajidin](https://github.com/nhajidin)) - - [#983](https://github.com/facebook/create-react-app/pull/983) Remove custom babel-loader cache dir config. ([@fson](https://github.com/fson)) -- `babel-preset-react-app` - - [#1052](https://github.com/facebook/create-react-app/pull/1052) Remove unnecessary transform plugins for object spread to work. ([@valscion](https://github.com/valscion)) - - [#992](https://github.com/facebook/create-react-app/pull/992) Explain the usage of react-jsx-source & react-jsx-self. ([@bboysathish](https://github.com/bboysathish)) - - [#1051](https://github.com/facebook/create-react-app/pull/1051) Update babel-present-env and use node: 'current' as target. ([@valscion](https://github.com/valscion)) - -#### Committers: 27 - -- Adam Stankiewicz ([sheerun](https://github.com/sheerun)) -- Alice Rose ([heldinz](https://github.com/heldinz)) -- Arunoda Susiripala ([arunoda](https://github.com/arunoda)) -- Brian Ng ([existentialism](https://github.com/existentialism)) -- Daniel Rech ([dmr](https://github.com/dmr)) -- Dave Ceddia ([dceddia](https://github.com/dceddia)) -- David Ernst ([dsernst](https://github.com/dsernst)) -- Dirk-Jan Rutten ([excitement-engineer](https://github.com/excitement-engineer)) -- Einar Löve ([einarlove](https://github.com/einarlove)) -- Fabrizio Castellarin ([EnoahNetzach](https://github.com/EnoahNetzach)) -- Fatih ([frontsideair](https://github.com/frontsideair)) -- Ian McNally ([ianmcnally](https://github.com/ianmcnally)) -- Jurie Horneman ([jhorneman](https://github.com/jhorneman)) -- Kyle Mathews ([KyleAMathews](https://github.com/KyleAMathews)) -- Leo Wong ([gnowoel](https://github.com/gnowoel)) -- Marc Garreau ([marcgarreau](https://github.com/marcgarreau)) -- Nazim Hajidin ([nhajidin](https://github.com/nhajidin)) -- Orta ([orta](https://github.com/orta)) -- Patrick Mackinder ([pmackcode](https://github.com/pmackcode)) -- Sandro Padin ([spadin](https://github.com/spadin)) -- Sathish ([bboysathish](https://github.com/bboysathish)) -- Stefan ([bebbi](https://github.com/bebbi)) -- Swizec Teller ([Swizec](https://github.com/Swizec)) -- Vadzim ([vadzim](https://github.com/vadzim)) -- Vesa Laakso ([valscion](https://github.com/valscion)) -- Ville Immonen ([fson](https://github.com/fson)) -- [ekaradon](https://github.com/ekaradon) - -### Migrating from 0.7.0 to 0.8.0 - -You may optionally update the global command (it’s not required, but it adds Yarn support for new projects): - -```sh -npm install -g create-react-app@1.0.0 -``` - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@0.8.0 -``` - -## 0.7.0 (October 22, 2016) - -Thanks to [@fson](https://github.com/fson) for cutting this release. - -### Build Dependency (`react-scripts`) - -- Updates Jest to [version 16.0](https://facebook.github.io/jest/blog/2016/10/03/jest-16.html), with an upgraded CLI, improved snapshot testing, new matchers and more. ([@chase](https://github.com/chase) in [#858](https://github.com/facebook/create-react-app/pull/858)) -- Test setup file `src/setupTests.js` is now called after test framework initialization to support loading custom matchers. ([@just-boris](https://github.com/just-boris) in [#846](https://github.com/facebook/create-react-app/pull/846)) -- Build command shows better instructions for deploying the app to GitHub Pages ([@Janpot](https://github.com/Janpot) in [#841](https://github.com/facebook/create-react-app/pull/841)) -- Build command now generates an asset manifest with mappings from each filename to its final output filename. ([@lukyth](https://github.com/lukyth) in [#891](https://github.com/facebook/create-react-app/pull/891)) -- Build command exits, if there are errors from UglifyJS ([@pdillon](https://github.com/pdillon) in [#859](https://github.com/facebook/create-react-app/pull/859)) -- Eject output is more beautiful now. ([@azakordonets](https://github.com/azakordonets) in [#769](https://github.com/facebook/create-react-app/pull/769)) -- Fixes opening the app in a new tab in Chrome. ([@unixdev](https://github.com/unixdev) in [#831](https://github.com/facebook/create-react-app/pull/831)) -- Fixes environment variables not being defined as normal properties of the `process.env` object. ([@dvkndn](https://github.com/dvkndn) in [#807](https://github.com/facebook/create-react-app/pull/807)) -- Fixes PostCSS autoprefixer not processing CSS files imported with CSS `@import` statements. ([@nhunzaker](https://github.com/nhunzaker) in [#929](https://github.com/facebook/create-react-app/pull/929)) - -### ESLint Config (`eslint-config-react-app`) - -- Adds `import/no-webpack-loader-syntax` rule that forbids using custom webpack specific syntax to specify webpack loaders in import statements. ([@fson](https://github.com/fson) in [#803](https://github.com/facebook/create-react-app/pull/803)) -- `react/react-in-jsx-scope` rule ("React must be in scope") is now an error. ([@gaearon](https://github.com/gaearon) in [#822](https://github.com/facebook/create-react-app/pull/822)) -- `no-unused-expressions` rule now allows the use of short circuit and ternary expressions. ([@cannona](https://github.com/cannona) in [#724](https://github.com/facebook/create-react-app/pull/724)) - -### Babel Preset (`babel-preset-react-app`) - -- The preset now detects the Node.js version in test environment and disables unnecessary ES2015 transforms using `babel-preset-env`. ([@shubheksha](https://github.com/shubheksha) in [#878](https://github.com/facebook/create-react-app/pull/878), [@JeffreyATW](https://github.com/JeffreyATW) in [#927 - ](https://github.com/facebook/create-react-app/pull/927)) -- Fixes a duplicate dependency on `babel-plugin-transform-regenerator`. ([@akofman](https://github.com/akofman) in [#864](https://github.com/facebook/create-react-app/pull/864)) - -### Utilities (`react-dev-utils`) - -- The error overlay is now disposed after fixing linting errors. ([@jarlef](https://github.com/jarlef) in [#856](https://github.com/facebook/create-react-app/pull/856)) -- Adds support for webpack 2 to `webpackHotDevClient`. ([@michalkvasnicak](https://github.com/michalkvasnicak) in [#840](https://github.com/facebook/create-react-app/pull/840)) - -### Global CLI (`create-react-app`) - -- Adds support for passing a scoped package name to the `--scripts-version` argument. ([@pdillon](https://github.com/pdillon) in [#826](https://github.com/facebook/create-react-app/pull/826)) -- Fixes installing pre-release versions using a tarball URL with the `--scripts-version` argument. ([@jihchi](https://github.com/jihchi) in [#876](https://github.com/facebook/create-react-app/pull/876)) - -### Migrating from 0.6.1 to 0.7.0 - -You may optionally update the global command (it’s not required): - -```sh -npm install -g create-react-app@0.6.0 -``` - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@0.7.0 -``` - -### Breaking Change in 0.7.0 - -#### Updating Snapshots - -Jest 16 includes [improvements to snapshot testing and changes to the snapshot format](https://facebook.github.io/jest/blog/2016/10/03/jest-16.html#snapshot-updates). If your project uses snapshot testing, you'll need to update the snapshot files. To update the snapshots, run: - -```sh -npm test -- -u -``` - -## 0.6.1 (September 27, 2016) - -### Build Dependency (`react-scripts`) - -- Babel and ESLint configuration is now placed into `package.json` after ejecting. ([@montogeek](https://github.com/montogeek) in [#773](https://github.com/facebook/create-react-app/pull/773)) - -### Utilities (`react-dev-utils`) - -- Fixes the syntax error overlay padding. ([@fson](https://github.com/fson) in [#758](https://github.com/facebook/create-react-app/pull/758)) - -### Migrating from 0.6.0 to 0.6.1 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@0.6.1 -``` - -## 0.6.0 (September 25, 2016) - -### Build Dependency (`react-scripts`) - -- Adds an overlay for syntax errors in development. ([@gaearon](https://github.com/gaearon) in [#744](https://github.com/facebook/create-react-app/pull/744)) - -### Utilities (`react-dev-utils`) - -- Adds an alternative WebpackDevServer client that displays the error overlay. ([@gaearon](https://github.com/gaearon) in [#744](https://github.com/facebook/create-react-app/pull/744)) - -### Migrating from 0.5.1 to 0.6.0 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@0.6.0 -``` - -**Note: If the project fails to start, remove `node_modules`, ensure `react-scripts` is `0.6.0` in your `package.json`, and run `npm install` again. There seems to be an [npm bug](https://github.com/npm/npm/issues/14073) affecting this update.** - -## 0.5.1 (September 23, 2016) - -### Build Dependency (`react-scripts`) - -- Updates `react-dev-utils` dependency - -### Utilities (`react-dev-utils`) - -- Fixes `%PUBLIC_URL%` replacement to work when specified multiple times. ([@fson](https://github.com/fson) in [#731](https://github.com/facebook/create-react-app/pull/731)) - -### Migrating from 0.5.0 to 0.5.1 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@0.5.1 -``` - -## 0.5.0 (September 23, 2016) - -### Build Dependency (`react-scripts`) - -- Adds [support for `public` folder](https://github.com/facebook/create-react-app/blob/main/packages/react-scripts/template/README.md#using-the-public-folder) with arbitrary assets. ([@gaearon](https://github.com/gaearon) in [#703](https://github.com/facebook/create-react-app/pull/703)) -- You can now [specify defaults](https://github.com/facebook/create-react-app/blob/main/packages/react-scripts/template/README.md#adding-development-environment-variables-in-env) for environment variables with `.env` file. ([@ayrton](https://github.com/ayrton) in [#695](https://github.com/facebook/create-react-app/pull/695)) -- Ejecting now generates proper `.babelrc` and `.eslintrc`. ([@fson](https://github.com/fson) in [#689](https://github.com/facebook/create-react-app/pull/689), [@gaearon](https://github.com/gaearon) in [#705](https://github.com/facebook/create-react-app/pull/705)) -- Some React warnings now [include the component stacktrace](https://twitter.com/dan_abramov/status/779308833399332864). ([@gaearon](https://github.com/gaearon) in [#716](https://github.com/facebook/create-react-app/pull/716)) -- `npm start` doesn’t fail in a composed Docker container. ([@arekkas](https://github.com/arekkas) in [#711](https://github.com/facebook/create-react-app/issues/711)) -- The projects generated with `eject` are now cleaner. ([@gaearon](https://github.com/gaearon) in [#723](https://github.com/facebook/create-react-app/pull/723)) -- The project is now managed as a monorepo. ([@ryanyogan](https://github.com/ryanyogan) in [#419](https://github.com/facebook/create-react-app/pull/419), [@fson](https://github.com/fson) in [#678](https://github.com/facebook/create-react-app/pull/678)) - -### ESLint Config (`eslint-config-react-app`) - -- Published for the first time! ([@fson](https://github.com/fson) in [#689](https://github.com/facebook/create-react-app/pull/689)) -- Added [`react/no-danger-with-children`](https://github.com/yannickcr/eslint-plugin-react/blob/v6.3.0/docs/rules/no-danger-with-children.md) and [`react/style-prop-object`](https://github.com/yannickcr/eslint-plugin-react/blob/v6.3.0/docs/rules/style-prop-object.md) rules. ([@fson](https://github.com/fson) in [#696](https://github.com/facebook/create-react-app/pull/696)) - -### Babel Preset (`babel-preset-react-app`) - -- Published for the first time! ([@fson](https://github.com/fson) in [#701](https://github.com/facebook/create-react-app/pull/701)) - -### Utilities (`react-dev-utils`) - -- Published for the first time! ([@gaearon](https://github.com/gaearon) in [#723](https://github.com/facebook/create-react-app/pull/723)) - -### Global CLI (`create-react-app`) - -- Added `README` to npm. There were no other changes. - -### Migrating from 0.4.3 to 0.5.0 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@0.5.0 -``` - -### Breaking Changes in 0.5.0 - -#### Global ESLint Plugin Versions - -If you used a global ESLint installation for the editor integration, you’ll need to install [these versions of global ESLint packages](https://github.com/facebook/create-react-app/blob/c092086b1b256fd081f10744f90d216dd5217e29/packages/eslint-config-react-app/package.json#L14-L19). - -#### Moving `index.html` into `public` Folder - -You’ll also need to create a new folder called `public` in the root of your project. Then, move `index.html` and files it references (such as a favicon) into that folder. - -You can no longer reference any files from `./src` in `index.html`. Instead, `public/index.html` can now only reference files other inside of the `public` folder using a special variable called `%PUBLIC_URL%`. - -For example, instead of: - -```html - -``` - -You would need to move both `index.html` and `src/favicon.ico` into the `public` folder, and change `` to look like this: - -```html - -``` - -This ensures it become a part of the build output, and resolves correctly both with client-side routing and non-root `homepage` in `package.json`. Read more about [using the `public` folder](https://github.com/facebook/create-react-app/blob/main/packages/react-scripts/template/README.md#using-the-public-folder) and [why these changes were made](https://github.com/facebook/create-react-app/pull/703). - -## 0.4.3 (September 18, 2016) - -This is a hotfix release for a broken package.
-It contained no changes to the code. - -### Build Dependency (`react-scripts`) - -- Fixes a packaging issue that affected npm 2. ([#676](https://github.com/facebook/create-react-app/issues/676)) - -### Migrating from 0.4.2 to 0.4.3 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@0.4.3 -``` - -## 0.4.2 (September 18, 2016) - -### Build Dependency (`react-scripts`) - -- Lint output in editor is now opt-in because, due to [this ESLint issue](https://github.com/eslint/eslint/issues/3458), it is broken by default in Atom. ([@fson](https://github.com/fson) in [#649](https://github.com/facebook/create-react-app/pull/649)) -- Fixes an issue causing compile errors when project folder is inside a symlink. ([@motiz88](https://github.com/motiz88) in [#648](https://github.com/facebook/create-react-app/pull/648)) -- You can now import `jpeg`, `wav`, `mp3`, `m4a`, `aac`, and `oga`. ([@mareksuscak](https://github.com/mareksuscak) in [#624](https://github.com/facebook/create-react-app/pull/624), [@danharper](https://github.com/danharper) in [#665](https://github.com/facebook/create-react-app/pull/665)) -- Fixes false positives caused by the case sensitive import warning on Windows. ([@Urthen](https://github.com/Urthen) in [#593](https://github.com/facebook/create-react-app/pull/593)) -- With Docker, `*.json.gzip` files are no longer created in the project folder. ([@thangngoc89](https://github.com/thangngoc89) in [#620](https://github.com/facebook/create-react-app/pull/620)) -- Proxy network errors now abort requests instead of hanging. ([@cloudmu](https://github.com/cloudmu) in [#588](https://github.com/facebook/create-react-app/pull/588)) -- Connection to the development server does not get interrupted in HTTPS mode. ([@dceddia](https://github.com/dceddia) in [#652](https://github.com/facebook/create-react-app/pull/652)) -- Unsupported Node versions now print a warning. ([@fson](https://github.com/fson) in [#575](https://github.com/facebook/create-react-app/pull/575)) -- Importing assets with special characters like `@` now works with tests. ([@fson](https://github.com/fson) in [#584](https://github.com/facebook/create-react-app/pull/584)) -- Undefined variable lint rule is promoted from a warning to an error. ([@gaearon](https://github.com/gaearon) in [#669](https://github.com/facebook/create-react-app/pull/669)) -- Variables starting with underscore no longer trigger the “unused variable” rule. ([@valscion](https://github.com/valscion) in [#640](https://github.com/facebook/create-react-app/pull/640)) -- We now print a friendly error when required files are missing. ([@vnctaing](https://github.com/vnctaing) in [#653](https://github.com/facebook/create-react-app/pull/653)) -- The output after creating a project is better formatted. ([@btnwtn](https://github.com/btnwtn) in [#629](https://github.com/facebook/create-react-app/pull/629)) -- Development server logs are less noisy. ([@gaearon](https://github.com/gaearon) in [122068](https://github.com/facebook/create-react-app/commit/1220683276dd9eb2f2719aece7f40bf2ffb397b4)) - -### Global CLI (`create-react-app`) - -- It now runs on early Node versions to print a friendly warning instead of crashing. ([@sotojuan](https://github.com/sotojuan) in [fc3ab4](https://github.com/facebook/create-react-app/commit/fc3ab46d2a54f142f9287ce7de9ab2fc2514487d)) -- We now print a friendly message when you create a project with invalid name. ([@mareksuscak](https://github.com/mareksuscak) in [#628](https://github.com/facebook/create-react-app/pull/628)) -- Passing a custom fork of `react-scripts` to `create-react-app` with `--scripts-version` works again. ([@yesmeck](https://github.com/yesmeck) in [#632](https://github.com/facebook/create-react-app/pull/632)) - -### Migrating from 0.4.1 to 0.4.2 - -You may optionally update the global command (it’s not required): - -```sh -npm install -g create-react-app@0.4.2 -``` - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@0.4.2 -``` - -## 0.4.1 (September 3, 2016) - -### Build Dependency (`react-scripts`) - -- We now support (but [don’t recommend](https://github.com/facebook/create-react-app/issues/87#issuecomment-234627904)) `.jsx` file extension. ([@tizmagik](https://github.com/tizmagik) in [#563](https://github.com/facebook/create-react-app/pull/563)) -- Proxy request errors are now printed to the console. ([@cloudmu](https://github.com/cloudmu) in [#502](https://github.com/facebook/create-react-app/pull/502)) - -### Migrating from 0.4.0 to 0.4.1 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@0.4.1 -``` - -## 0.4.0 (September 2, 2016) - -### Build Dependency (`react-scripts`) - -- **Breaking Change:** Disabled implicit serving of source files in development. ([@gaearon](https://github.com/gaearon) in [#551](https://github.com/facebook/create-react-app/pull/551)) -- You can use `NODE_PATH` environment variable for absolute `import` paths. ([@jimmyhmiller](https://github.com/jimmyhmiller) in [#476](https://github.com/facebook/create-react-app/pull/476)) -- If `src/setupTests.js` exists, it will be used to setup the test environment. ([@gaelduplessix](https://github.com/gaelduplessix) in [#548](https://github.com/facebook/create-react-app/pull/548)) -- If `HTTPS` environment variable is set to `true`, development server will run in HTTPS mode. ([@dceddia](https://github.com/dceddia) in [#552](https://github.com/facebook/create-react-app/pull/552)) - -### Migrating from 0.3.1 to 0.4.0 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@0.4.0 -``` - -### Breaking Change in 0.4.0 - -Paths like `/src/somefile.png` used to be served in development, but only by accident. They never worked in production builds. Since 0.4.0, we [don’t serve static files by default in development anymore either](https://github.com/facebook/create-react-app/pull/551). This removes a dangerous inconsistency that we never intentionally supported. - -If you need a static file to be part for the build, [import it from JavaScript and you will get its filename](https://github.com/facebook/create-react-app/blob/main/template/README.md#adding-images-and-fonts). This ensures it gets included into the production build as well, and its filename contains the content hash. - -If you used static files with ``, [read this new guide](https://github.com/facebook/create-react-app/blob/main/template/README.md#referring-to-static-assets-from-link-href) on how to make sure these files get included into the builds. For example, you can replace `` with ``, and then webpack will recognize it and include it into the build. - -If you referenced some other files from `index.html`, please file an issue to discuss your use case. In the meantime, you can serve them from a separate static server until your use case is supported. - -## 0.3.1 (September 2, 2016) - -### Build Dependency (`react-scripts`) - -- Bumps Jest dependency to fix a few issues discovered yesterday. ([@cpojer](https://github.com/cpojer) in [facebook/jest#1580](https://github.com/facebook/jest/pull/1580), [@insin](https://github.com/insin) in [facebook/jest#1574](https://github.com/facebook/jest/pull/1574)) - -### Migrating from 0.3.0 to 0.3.1 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@0.3.1 -``` - -## 0.3.0 (September 1, 2016) - -### Build Dependency (`react-scripts`) - -- Testing is [now supported](https://github.com/facebook/create-react-app/blob/main/template/README.md#running-tests)! ([Jest project contributors](https://github.com/facebook/jest/pulls?q=is%3Apr+is%3Aclosed), [@cpojer](https://github.com/cpojer) in [#250](https://github.com/facebook/create-react-app/pull/250), [@gaearon](https://github.com/gaearon) in [#378](https://github.com/facebook/create-react-app/pull/378), [#530](https://github.com/facebook/create-react-app/pull/530), [#533](https://github.com/facebook/create-react-app/pull/533)) -- Static files such as CSS, images, and fonts, can now exist outside `src` directory. ([@fson](https://github.com/fson) in [#504](https://github.com/facebook/create-react-app/pull/504)) -- **Breaking Change:** Local paths in `` in `index.html` will now be correctly resolved, so deleting `favicon.ico` is not an error anymore. ([@andreypopp](https://github.com/andreypopp) in [#428](https://github.com/facebook/create-react-app/pull/428)) -- Removed an annoying lint rule that warned for `
this.node = node}>`. ([@mrscobbler](https://github.com/mrscobbler) in [#529](https://github.com/facebook/create-react-app/pull/529)) -- Temporarily disabled `react-constant-elements` Babel transform because of its bugs. ([@gaearon](https://github.com/gaearon) in [#534](https://github.com/facebook/create-react-app/pull/534)) -- Fixed a permission issue with Docker. ([@gaearon](https://github.com/gaearon) in [73c940](https://github.com/facebook/create-react-app/commit/73c940a73205d761230f8d6bf81ecfd460ba28a9)) -- Fixed an issue with generator syntax in Jest that occurred in an alpha release. ([@gaearon](https://github.com/gaearon) in [#535](https://github.com/facebook/create-react-app/pull/535)) - -### Global CLI (`create-react-app`) - -- You can now create a project in a folder that already contains an `.idea` folder, which is necessary for future WebStorm integration. ([@denofevil](https://github.com/denofevil) in [#522](https://github.com/facebook/create-react-app/pull/522)) - -### Migrating from 0.2.3 to 0.3.0 - -You may optionally update the global command (it’s not required): - -```sh -npm install -g create-react-app@0.3.0 -``` - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@0.3.0 -``` - -#### Breaking Change - -Now `favicon.ico` is not treated specially anymore.
-If you use it, move it to `src` and add the following line to `` in your HTML: - -```html - -``` - -#### New Feature - -Since 0.3.0 added a test runner, we recommend that you add it to the `scripts` section of your `package.json` like this: - -```js - // ... - "scripts": { - // ... - "test": "react-scripts test --env=jsdom" - } -``` - -[Then read the testing guide to learn more about using it!](https://github.com/facebook/create-react-app/blob/main/template/README.md#running-tests) - -## 0.2.3 (August 25, 2016) - -### Build Dependency (`react-scripts`) - -- You can now [proxy requests to an API server](https://github.com/facebook/create-react-app/blob/ef94b0561d5afb9b50b905fa5cd3f94e965c69c0/template/README.md#proxying-api-requests-in-development) without worrying about CORS. ([@gaearon](https://github.com/gaearon) in [#282](https://github.com/facebook/create-react-app/pull/282)) -- You can now [pass custom environment variables](https://github.com/facebook/create-react-app/blob/ef94b0561d5afb9b50b905fa5cd3f94e965c69c0/template/README.md#adding-custom-environment-variables) to your application. ([@eliperelman](https://github.com/eliperelman) in [#342](https://github.com/facebook/create-react-app/pull/342)) -- You can now [use `async` and `await`](https://ponyfoo.com/articles/understanding-javascript-async-await) syntax. ([@gaearon](https://github.com/gaearon) in [#327](https://github.com/facebook/create-react-app/pull/327), [@fson](https://github.com/fson) in [#332](https://github.com/facebook/create-react-app/pull/332)) -- Paths with period in them now load successfully on the development server. ([@mxstbr](https://github.com/mxstbr) in [#422](https://github.com/facebook/create-react-app/pull/422)) -- Images with `.webp` extension are now supported. ([@gafemoyano](https://github.com/gafemoyano) in [#458](https://github.com/facebook/create-react-app/pull/458)) -- The most recent version of React is now added to `package.json`. ([@wdhorton](https://github.com/wdhorton) in [#477](https://github.com/facebook/create-react-app/pull/477)) -- Babel configuration is simplified. ([@kripod](https://github.com/kripod) in [#490](https://github.com/facebook/create-react-app/pull/490)) - -### Migrating from 0.2.2 to 0.2.3 - -Update `react-scripts` to point to `0.2.3` in your `package.json` and run `npm install`. You shouldn’t need to do anything else. - -Newly created projects will use `0.2.3` automatically. You **don’t** need to update the global `create-react-app` CLI itself. It stays at `0.2.0` for now because it doesn’t have any changes. - -## 0.2.2 (August 22, 2016) - -### Build Dependency (`react-scripts`) - -- When the bundle size changes, we now display the difference after build. ([@elijahmanor](https://github.com/elijahmanor) in [#340](https://github.com/facebook/create-react-app/pull/340)) -- `npm install`ing a missing dependency now forces a rebuild. ([@gaearon](https://github.com/gaearon) in [#349](https://github.com/facebook/create-react-app/pull/349)) -- Autoprefixer config now includes more commonly supported browsers. ([@kripod](https://github.com/kripod) in [#345](https://github.com/facebook/create-react-app/pull/345)) -- All the configuration is now documented inline so ejecting doesn’t leave you in the dark. ([@gaearon](https://github.com/gaearon) in [#362](https://github.com/facebook/create-react-app/pull/362)) -- `Object.assign()` polyfill is now bundled by default. ([@gaearon](https://github.com/gaearon) in [#399](https://github.com/facebook/create-react-app/pull/399)) -- [React Native Web](https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/) now works out of the box. ([@grigio](https://github.com/grigio) in [#407](https://github.com/facebook/create-react-app/pull/407)) -- Same asset filenames in different folders don’t confuse the server now. ([@arunoda](https://github.com/arunoda) in [#446](https://github.com/facebook/create-react-app/pull/446)) -- The `otf` font format is now supported. ([@A-gambit](https://github.com/A-gambit) in [#434](https://github.com/facebook/create-react-app/pull/434)) -- The `new-cap` linting rule has been disabled thanks to feedback from Immutable.js users. ([@rricard](https://github.com/rricard) in [#470](https://github.com/facebook/create-react-app/pull/470)) - -### Migrating from 0.2.1 to 0.2.2 - -Update `react-scripts` to point to `0.2.2` in your `package.json` and run `npm install`. You shouldn’t need to do anything else. - -Newly created projects will use `0.2.2` automatically. You **don’t** need to update the global `create-react-app` CLI itself. It stays at `0.2.0` for now because it doesn’t have any changes. - -## 0.2.1 (August 1, 2016) - -### Build Dependency (`react-scripts`) - -- Fixes an issue with `npm start` taking a very long time on OS X with Firewall enabled ([@gaearon](https://github.com/gaearon) in [#319](https://github.com/facebook/create-react-app/pull/319)) -- Fixes an issue with webpack eating a lot of CPU in some cases ([@dceddia](https://github.com/dceddia) in [#294](https://github.com/facebook/create-react-app/pull/294)) -- We now warn if you import a file with mismatched casing because this breaks the watcher ([@alexzherdev](https://github.com/alexzherdev) in [#266](https://github.com/facebook/create-react-app/pull/266)) -- CSS files specifying `?v=` after asset filenames, such as Font Awesome, now works correctly ([@alexzherdev](https://github.com/alexzherdev) in [#298](https://github.com/facebook/create-react-app/pull/298)) -- Issues with `npm link`ing `react-scripts` have been fixed ([@dallonf](https://github.com/dallonf) in [#277](https://github.com/facebook/create-react-app/pull/277)) -- We now use `/static` prefix for assets both in development and production ([@gaearon](https://github.com/gaearon) in [#278](https://github.com/facebook/create-react-app/pull/278)) - -### Migrating from 0.2.0 to 0.2.1 - -Update `react-scripts` to point to `0.2.1` in your `package.json` and run `npm install`. You shouldn’t need to do anything else. If you see a warning about wrong file casing next time you `npm start`, fix your imports to use the correct filename casing. - -Newly created projects will use `0.2.1` automatically. You **don’t** need to update the global `create-react-app` CLI itself. It stays at `0.2.0` for now because it doesn’t have any changes. - -## 0.2.0 (July 28, 2016) - -### Build Dependency (`react-scripts`) - -- You can now enable deployment to GitHub Pages by adding `homepage` field to `package.json` ([@dhruska](https://github.com/dhruska) in [#94](https://github.com/facebook/create-react-app/pull/94)) -- Development server now runs on `0.0.0.0` and works with VirtualBox ([@JWo1F](https://github.com/JWo1F) in [#128](https://github.com/facebook/create-react-app/pull/128)) -- Cloud9 and Nitrous online IDEs are now supported ([@gaearon](https://github.com/gaearon) in [2fe84e](https://github.com/facebook/create-react-app/commit/2fe84ecded55f1d5258d91f9c2c07698ae0d2fb4)) -- When `3000` port is taken, we offer to use another port ([@chocnut](https://github.com/chocnut) in [#101](https://github.com/facebook/create-react-app/pull/101), [2edf21](https://github.com/facebook/create-react-app/commit/2edf2180f2aa6bf647807d0b1fcd95f4cfe4a558)) -- You can now `import` CSS files from npm modules ([@glennreyes](https://github.com/glennreyes) in [#105](https://github.com/facebook/create-react-app/pull/105), [@breaddevil](https://github.com/breaddevil) in [#178](https://github.com/facebook/create-react-app/pull/178)) -- `fetch` and `Promise` polyfills are now always included ([@gaearon](https://github.com/gaearon) in [#235](https://github.com/facebook/create-react-app/pull/235)) -- Regenerator runtime is now included if you use ES6 generators ([@gaearon](https://github.com/gaearon) in [#238](https://github.com/facebook/create-react-app/pull/238)) -- Generated project now contains `.gitignore` ([@npverni](https://github.com/npverni) in [#79](https://github.com/facebook/create-react-app/pull/79), [@chibicode](https://github.com/chibicode) in [#112](https://github.com/facebook/create-react-app/pull/112)) -- ESLint config is now more compatible with Flow ([@gaearon](https://github.com/gaearon) in [#261](https://github.com/facebook/create-react-app/pull/261)) -- A stylistic lint rule about method naming has been removed ([@mxstbr](https://github.com/mxstbr) in [#152](https://github.com/facebook/create-react-app/pull/157)) -- A few unobtrusive accessibility lint rules have been added ([@evcohen](https://github.com/evcohen) in [#175](https://github.com/facebook/create-react-app/pull/175)) -- A `.babelrc` in parent directory no longer causes an error ([@alexzherdev](https://github.com/alexzherdev) in [#236](https://github.com/facebook/create-react-app/pull/236)) -- Files with `.json` extension are now discovered ([@gaearon](https://github.com/gaearon) in [a11d6a](https://github.com/facebook/create-react-app/commit/a11d6a398f487f9163880dd34667b1d3e14b147a)) -- Bug fixes from transitive dependencies are included ([#126](https://github.com/facebook/create-react-app/issues/126)) -- Linting now works with IDEs if you follow [these](https://github.com/facebook/create-react-app/blob/main/template/README.md#display-lint-output-in-the-editor) instructions ([@keyanzhang](https://github.com/keyanzhang) in [#149](https://github.com/facebook/create-react-app/pull/149)) -- After building, we now print gzipped bundle size ([@lvwrence](https://github.com/lvwrence) in [#229](https://github.com/facebook/create-react-app/pull/229)) - -### Global CLI (`create-react-app`) - -- It enforces that you have Node >= 4 ([@conorhastings](https://github.com/conorhastings) in [#88](https://github.com/facebook/create-react-app/pull/88)) -- It handles `--version` flag correctly ([@mxstbr](https://github.com/mxstbr) in [#152](https://github.com/facebook/create-react-app/pull/152)) - -### Migrating from 0.1.0 to 0.2.0 - -You may optionally update the global command (it’s not required): - -```sh -npm install -g create-react-app@0.2.0 -``` - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@0.2.0 -``` - -You may need to fix a few lint warnings about missing `` tag, but everything else should work out of the box. If you intend to deploy your site to GitHub Pages, you may now [add `homepage` field to `package.json`](https://github.com/facebook/create-react-app/blob/main/template/README.md#deploy-to-github-pages). If you had [issues with integrating editor linter plugins](https://github.com/facebook/create-react-app/issues/124), follow [these new instructions](https://github.com/facebook/create-react-app/blob/main/template/README.md#display-lint-output-in-the-editor). - -## 0.1.0 (July 22, 2016) - -- Initial public release diff --git a/CHANGELOG-1.x.md b/CHANGELOG-1.x.md deleted file mode 100644 index f797a8e54a3..00000000000 --- a/CHANGELOG-1.x.md +++ /dev/null @@ -1,1870 +0,0 @@ -## 2.0.3 and Newer Versions - -**Please refer to [CHANGELOG-2.x.md](./CHANGELOG-2.x.md) for the 2.x range, and [CHANGELOG.md](CHANGELOG.md) for the newer versions.** - -## 1.1.5 (August 24, 2018) - -- `react-scripts` - - - Update the `webpack-dev-server` dependency - -- `react-dev-utils` - - - [#4866](https://github.com/facebook/create-react-app/pull/4866) Fix a Windows-only vulnerability (`CVE-2018-6342`) in the development server ([@acdlite](https://github.com/acdlite)) - - Update the `sockjs-client` dependency - -#### Committers: 1 - -- Andrew Clark ([acdlite](https://github.com/acdlite)) - -### Migrating from 1.1.4 to 1.1.5 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save --save-exact react-scripts@1.1.5 -``` - -or - -```sh -yarn add --exact react-scripts@1.1.5 -``` - -## 1.1.4 (April 3, 2018) - -#### :bug: Bug Fix - -- `react-dev-utils` - - - [#4250](https://github.com/facebook/create-react-app/pull/4250) Upgrade `detect-port-alt` to fix [#4189](https://github.com/facebook/create-react-app/issues/4189). ([@Timer](https://github.com/Timer)) - -#### Committers: 1 - -- Joe Haddad ([Timer](https://github.com/Timer)) - -### Migrating from 1.1.3 to 1.1.4 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save --save-exact react-scripts@1.1.4 -``` - -or - -```sh -yarn add --exact react-scripts@1.1.4 -``` - -## 1.1.3 (April 3, 2018) - -#### :bug: Bug Fix - -- `react-scripts` - - - [#4247](https://github.com/facebook/create-react-app/pull/4247) Fix `environment.dispose is not a function` error caused by a Jest bug. ([@gaearon](https://github.com/gaearon)) - -#### Committers: 1 - -- Dan Abramov ([gaearon](https://github.com/gaearon)) - -### Migrating from 1.1.2 to 1.1.3 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save --save-exact react-scripts@1.1.3 -``` - -or - -```sh -yarn add --exact react-scripts@1.1.3 -``` - -## 1.1.2 (April 3, 2018) - -#### :bug: Bug Fix - -- `react-scripts` - - - [#4085](https://github.com/facebook/create-react-app/pull/4085) Resolve `.js` before `.mjs` files to unbreak dependencies with native ESM support. ([@leebyron](https://github.com/leebyron)) - -#### :memo: Documentation - -- `react-scripts` - - - [#4197](https://github.com/facebook/create-react-app/pull/4197) Add troubleshooting for Github Pages. ([@xnt](https://github.com/xnt)) - -#### Committers: 2 - -- Lee Byron ([leebyron](https://github.com/leebyron)) -- Vicente Plata ([xnt](https://github.com/xnt)) - -### Migrating from 1.1.1 to 1.1.2 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save --save-exact react-scripts@1.1.2 -``` - -or - -```sh -yarn add --exact react-scripts@1.1.2 -``` - -## 1.1.1 (February 2, 2018) - -#### :bug: Bug Fix - -- `react-scripts` - - [#4000](https://github.com/facebook/create-react-app/pull/4000) Fix escaping `$` in environment variables. ([@iansu](https://github.com/iansu)) - -#### :nail_care: Enhancement - -- `react-scripts` - - [#4006](https://github.com/facebook/create-react-app/pull/4006) Add Node 9 compatibility for `fsevents`. ([@gaearon](https://github.com/gaearon)) - -#### :memo: Documentation - -- `react-scripts` - - [#3971](https://github.com/facebook/create-react-app/pull/3971) Update instructions for continuous delivery with Netlify. ([@hubgit](https://github.com/hubgit)) - - [#3894](https://github.com/facebook/create-react-app/pull/3894) Include `{json,css}` files in prettier command. ([@reyronald](https://github.com/reyronald)) - -#### :house: Internal - -- `create-react-app` - - [#3853](https://github.com/facebook/create-react-app/pull/3853) pin envinfo version to 3.4.2. ([@bondz](https://github.com/bondz)) - -#### Committers: 6 - -- Alf Eaton ([hubgit](https://github.com/hubgit)) -- Bond ([bondz](https://github.com/bondz)) -- Dan Abramov ([gaearon](https://github.com/gaearon)) -- Ian Sutherland ([iansu](https://github.com/iansu)) -- Ronald Rey ([reyronald](https://github.com/reyronald)) - -### Migrating from 1.1.0 to 1.1.1 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save --save-exact react-scripts@1.1.1 -``` - -or - -```sh -yarn add --exact react-scripts@1.1.1 -``` - -## 1.1.0 (January 15, 2018) - -#### :rocket: New Feature - -- `react-scripts` - - - [#3387](https://github.com/facebook/create-react-app/pull/3387) Add support for variable expansion in `.env` files. ([@moos](https://github.com/moos)) - -- `react-error-overlay` - - - [#3474](https://github.com/facebook/create-react-app/pull/3474) Allow the error overlay to be unregistered. ([@Timer](https://github.com/Timer)) - -- `create-react-app` - - - [#3408](https://github.com/facebook/create-react-app/pull/3408) Add `--info` flag to help gather bug reports. ([@tabrindle](https://github.com/tabrindle)) - - [#3409](https://github.com/facebook/create-react-app/pull/3409) Add `--use-npm` flag to bypass Yarn even on systems that have it. ([@tabrindle](https://github.com/tabrindle)) - - [#3725](https://github.com/facebook/create-react-app/pull/3725) Extend `--scripts-version` to include `.tar.gz` format. ([@SaschaDens](https://github.com/SaschaDens)) - - [#3629](https://github.com/facebook/create-react-app/pull/3629) Allowing `"file:"` `--scripts-version` values. ([@GreenGremlin](https://github.com/GreenGremlin)) - -#### :bug: Bug Fix - -- `babel-preset-react-app`, `react-scripts` - - - [#3788](https://github.com/facebook/create-react-app/pull/3788) Fix object destructuring inside an array on Node 6. ([@gaearon](https://github.com/gaearon)) - -- `react-dev-utils` - - - [#3784](https://github.com/facebook/create-react-app/pull/3784) Detach browser process from the shell on Linux. ([@gaearon](https://github.com/gaearon)) - - [#3726](https://github.com/facebook/create-react-app/pull/3726) Use proxy for all request methods other than `GET`. ([@doshisid](https://github.com/doshisid)) - - [#3440](https://github.com/facebook/create-react-app/pull/3440) Print full directory name from `lsof`. ([@rmccue](https://github.com/rmccue)) - - [#2071](https://github.com/facebook/create-react-app/pull/2071) Fix broken console clearing on Windows. ([@danielverejan](https://github.com/danielverejan)) - - [#3686](https://github.com/facebook/create-react-app/pull/3686) Fix starting a project in directory with `++` in the name. ([@Norris1z](https://github.com/Norris1z)) - -- `create-react-app` - - - [#3320](https://github.com/facebook/create-react-app/pull/3320) Fix offline installation to respect proxy from `.npmrc`. ([@mdogadailo](https://github.com/mdogadailo)) - -- `react-scripts` - - - [#3537](https://github.com/facebook/create-react-app/pull/3537) Add `mjs` and `jsx` filename extensions to `file-loader` exclude pattern. ([@iansu](https://github.com/iansu)) - - [#3511](https://github.com/facebook/create-react-app/pull/3511) Unmount the component in the default generated test. ([@gaearon](https://github.com/gaearon)) - -#### :nail_care: Enhancement - -- `react-scripts` - - - [#3730](https://github.com/facebook/create-react-app/pull/3730) Print when `HOST` environment variable is set. ([@iansu](https://github.com/iansu)) - - [#3455](https://github.com/facebook/create-react-app/pull/3455) Add a localhost-only log message pointing folks to the PWA docs. ([@jeffposnick](https://github.com/jeffposnick)) - - [#3416](https://github.com/facebook/create-react-app/pull/3416) Improve eject message. ([@xjlim](https://github.com/xjlim)) - -- `create-react-app` - - - [#3740](https://github.com/facebook/create-react-app/pull/3740) Allow more non-conflicting files in initial project directory. ([@GreenGremlin](https://github.com/GreenGremlin)) - -- `react-dev-utils` - - - [#3104](https://github.com/facebook/create-react-app/pull/3104) Add link to deployment docs after build. ([@viankakrisna](https://github.com/viankakrisna)) - - [#3652](https://github.com/facebook/create-react-app/pull/3652) Add `code-insiders` to the editor list. ([@shrynx](https://github.com/shrynx)) - - [#3700](https://github.com/facebook/create-react-app/pull/3700) Add editor support for Sublime Dev & VSCode Insiders. ([@yyx990803](https://github.com/yyx990803)) - - [#3545](https://github.com/facebook/create-react-app/pull/3545) Autodetect MacVim editor. ([@gnapse](https://github.com/gnapse)) - -- `react-dev-utils`, `react-error-overlay` - - - [#3465](https://github.com/facebook/create-react-app/pull/3465) Open editor to exact column from build error overlay. ([@tharakawj](https://github.com/tharakawj)) - -- `react-dev-utils`, `react-scripts` - - - [#3721](https://github.com/facebook/create-react-app/pull/3721) Support setting `none` in `REACT_EDITOR` environment variable. ([@raerpo](https://github.com/raerpo)) - -- `eslint-config-react-app` - - - [#3716](https://github.com/facebook/create-react-app/pull/3716) Relax `no-cond-assign` rule. ([@gaearon](https://github.com/gaearon)) - -#### :memo: Documentation - -- User Guide - - - [#3659](https://github.com/facebook/create-react-app/pull/3659) Add info about service-worker and HTTP caching headers into Firebase section. ([@bobrosoft](https://github.com/bobrosoft)) - - [#3515](https://github.com/facebook/create-react-app/pull/3515) Add Powershell commands to README.md. ([@Gua-naiko-che](https://github.com/Gua-naiko-che)) - - [#3656](https://github.com/facebook/create-react-app/pull/3656) Better documentation for setupTests.js when ejecting. ([@dannycalleri](https://github.com/dannycalleri)) - - [#1791](https://github.com/facebook/create-react-app/pull/1791) Add link for automatic deployment to azure. ([@ulrikstrid](https://github.com/ulrikstrid)) - - [#3717](https://github.com/facebook/create-react-app/pull/3717) Update README.md. ([@maecapozzi](https://github.com/maecapozzi)) - - [#3710](https://github.com/facebook/create-react-app/pull/3710) Link to an explanation for forking react-scripts. ([@gaearon](https://github.com/gaearon)) - - [#3709](https://github.com/facebook/create-react-app/pull/3709) Document adding a router. ([@gaearon](https://github.com/gaearon)) - - [#3670](https://github.com/facebook/create-react-app/pull/3670) Fix typo in the User Guide. ([@qbahers](https://github.com/qbahers)) - - [#3645](https://github.com/facebook/create-react-app/pull/3645) Update README.md. ([@elie222](https://github.com/elie222)) - - [#3533](https://github.com/facebook/create-react-app/pull/3533) Use safer/more aesthetic syntax for setting environment variables on Windows. ([@cdanielsen](https://github.com/cdanielsen)) - - [#3605](https://github.com/facebook/create-react-app/pull/3605) Updated Debugging Tests for VSCode. ([@amadeogallardo](https://github.com/amadeogallardo)) - - [#3601](https://github.com/facebook/create-react-app/pull/3601) Fixed typo in webpack.config.dev.js. ([@nmenglund](https://github.com/nmenglund)) - - [#3576](https://github.com/facebook/create-react-app/pull/3576) Updates comment to reflect codebase. ([@rahulcs](https://github.com/rahulcs)) - - [#3510](https://github.com/facebook/create-react-app/pull/3510) Update User Guide with deploying to GitHub User pages. ([@aaronlna](https://github.com/aaronlna)) - - [#3503](https://github.com/facebook/create-react-app/pull/3503) Update Prettier editor integration link. ([@gaving](https://github.com/gaving)) - - [#3453](https://github.com/facebook/create-react-app/pull/3453) Fix dead links. ([@vannio](https://github.com/vannio)) - - [#2992](https://github.com/facebook/create-react-app/pull/2992) Docs: How to Debug Unit Tests. ([@MattMorgis](https://github.com/MattMorgis)) - -- Other - - - [#3729](https://github.com/facebook/create-react-app/pull/3729) Update README.md to note Neutrino's support of react components. ([@eliperelman](https://github.com/eliperelman)) - - [#2841](https://github.com/facebook/create-react-app/pull/2841) Documentation to help windows contributors. ([@Dubes](https://github.com/Dubes)) - - [#3489](https://github.com/facebook/create-react-app/pull/3489) Add link to nvm-windows. ([@davidgilbertson](https://github.com/davidgilbertson)) - -- `eslint-config-react-app` - - - [#3460](https://github.com/facebook/create-react-app/pull/3460) Fix broken link to `href-no-hash` eslint rule. ([@hazolsky](https://github.com/hazolsky)) - -#### :house: Internal - -- Other - - - [#3769](https://github.com/facebook/create-react-app/pull/3769) Enable Yarn check files. ([@Timer](https://github.com/Timer)) - - [#3756](https://github.com/facebook/create-react-app/pull/3756) Clean up changes to npm and yarn registry in E2E tests. ([@viankakrisna](https://github.com/viankakrisna)) - - [#3744](https://github.com/facebook/create-react-app/pull/3744) Use private registry in E2E tests. ([@Timer](https://github.com/Timer)) - - [#3738](https://github.com/facebook/create-react-app/pull/3738) Always use Yarn on CI. ([@gaearon](https://github.com/gaearon)) - - [#2309](https://github.com/facebook/create-react-app/pull/2309) Port `cra.sh` development task to javascript. ([@ianschmitz](https://github.com/ianschmitz)) - - [#3411](https://github.com/facebook/create-react-app/pull/3411) Simplify waiting for app start in E2E tests. ([@xjlim](https://github.com/xjlim)) - - [#3755](https://github.com/facebook/create-react-app/pull/3755) Switch to Yarn Workspaces. ([@gaearon](https://github.com/gaearon)) - - [#3757](https://github.com/facebook/create-react-app/pull/3757) Try updating Flow. ([@gaearon](https://github.com/gaearon)) - - [#3414](https://github.com/facebook/create-react-app/pull/3414) Export `dismissRuntimeErrors` function. ([@skidding](https://github.com/skidding)) - - [#3036](https://github.com/facebook/create-react-app/pull/3036) Cleaning up `printHostingInstructions` a bit. ([@GreenGremlin](https://github.com/GreenGremlin)) - - [#3514](https://github.com/facebook/create-react-app/pull/3514) Fix `FileSizeReporter` for multi build webpack setups. ([@iiska](https://github.com/iiska)) - - [#3362](https://github.com/facebook/create-react-app/pull/3362) Refactor extra watch options regex to `react-dev-utils`. ([@xjlim](https://github.com/xjlim)) - -#### Committers: 47 - -- Aaron Lamb ([aaronlna](https://github.com/aaronlna)) -- Ade Viankakrisna Fadlil ([viankakrisna](https://github.com/viankakrisna)) -- Amadeo Gallardo ([amadeogallardo](https://github.com/amadeogallardo)) -- Andy Kenward ([andykenward](https://github.com/andykenward)) -- Christian Danielsen ([cdanielsen](https://github.com/cdanielsen)) -- Clayton Ray ([iamclaytonray](https://github.com/iamclaytonray)) -- Dan Abramov ([gaearon](https://github.com/gaearon)) -- Daniel Verejan ([danielverejan](https://github.com/danielverejan)) -- Danny Calleri ([dannycalleri](https://github.com/dannycalleri)) -- David Boyne ([boyney123](https://github.com/boyney123)) -- David Gilbertson ([davidgilbertson](https://github.com/davidgilbertson)) -- Eli Perelman ([eliperelman](https://github.com/eliperelman)) -- Elie ([elie222](https://github.com/elie222)) -- Ernesto García ([gnapse](https://github.com/gnapse)) -- Evan You ([yyx990803](https://github.com/yyx990803)) -- Gavin Gilmour ([gaving](https://github.com/gaving)) -- Ian Schmitz ([ianschmitz](https://github.com/ianschmitz)) -- Ian Sutherland ([iansu](https://github.com/iansu)) -- JANG SUN HYUK ([wkdtjsgur100](https://github.com/wkdtjsgur100)) -- Jeffrey Posnick ([jeffposnick](https://github.com/jeffposnick)) -- Joe Haddad ([Timer](https://github.com/Timer)) -- Joe Lim ([xjlim](https://github.com/xjlim)) -- Jonathan ([GreenGremlin](https://github.com/GreenGremlin)) -- Juhamatti Niemelä ([iiska](https://github.com/iiska)) -- Mae Capozzi ([maecapozzi](https://github.com/maecapozzi)) -- Maksym Dogadailo ([mdogadailo](https://github.com/mdogadailo)) -- Mario Nebl ([marionebl](https://github.com/marionebl)) -- Matt Morgis ([MattMorgis](https://github.com/MattMorgis)) -- Misha Khokhlov ([hazolsky](https://github.com/hazolsky)) -- Moos ([moos](https://github.com/moos)) -- Nils Magnus Englund ([nmenglund](https://github.com/nmenglund)) -- Norris Oduro ([Norris1z](https://github.com/Norris1z)) -- Ovidiu Cherecheș ([skidding](https://github.com/skidding)) -- Quentin Bahers ([qbahers](https://github.com/qbahers)) -- Rafael E. Poveda ([raerpo](https://github.com/raerpo)) -- Rahul Chanila ([rahulcs](https://github.com/rahulcs)) -- Ryan McCue ([rmccue](https://github.com/rmccue)) -- Sascha Dens ([SaschaDens](https://github.com/SaschaDens)) -- Siddharth Doshi ([doshisid](https://github.com/doshisid)) -- Tao Gómez Gil ([Gua-naiko-che](https://github.com/Gua-naiko-che)) -- Tharaka Wijebandara ([tharakawj](https://github.com/tharakawj)) -- Trevor Brindle ([tabrindle](https://github.com/tabrindle)) -- Ulrik Strid ([ulrikstrid](https://github.com/ulrikstrid)) -- Vladimir Tolstikov ([bobrosoft](https://github.com/bobrosoft)) -- [Dubes](https://github.com/Dubes) -- [vannio](https://github.com/vannio) -- shrynx ([shrynx](https://github.com/shrynx)) - -### Migrating from 1.0.17 to 1.1.0 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save --save-exact react-scripts@1.1.0 -``` - -or - -```sh -yarn add --exact react-scripts@1.1.0 -``` - -## 1.0.17 (November 3, 2017) - -#### :nail_care: Enhancement - -- `react-scripts` - - - [#3401](https://github.com/facebook/create-react-app/pull/3401) Stop using a deprecated option. ([@gaearon](https://github.com/gaearon)) - -#### :memo: Documentation - -- `react-scripts` - - - [#3399](https://github.com/facebook/create-react-app/pull/3399) Add link to VS Code troubleshooting guide. ([@auchenberg](https://github.com/auchenberg)) - - [#3400](https://github.com/facebook/create-react-app/pull/3400) Update VS Code debug configuration. ([@auchenberg](https://github.com/auchenberg)) - -#### Committers: 3 - -- Dan Abramov ([gaearon](https://github.com/gaearon)) -- Kenneth Auchenberg ([auchenberg](https://github.com/auchenberg)) -- Loren Sands-Ramshaw ([lorensr](https://github.com/lorensr)) - -### Migrating from 1.0.16 to 1.0.17 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save --save-exact react-scripts@1.0.17 -``` - -or - -```sh -yarn add --exact react-scripts@1.0.17 -``` - -## 1.0.16 (October 31, 2017) 🎃 - -#### :bug: Bug Fix - -- `react-scripts` - - - [#3374](https://github.com/facebook/create-react-app/pull/3374) Set correct image type and sizes in `manifest.json`. ([@piotr-cz](https://github.com/piotr-cz)) - -- `react-dev-utils` - - - [#3368](https://github.com/facebook/create-react-app/pull/3368) Fix a crash in development mode in older browsers. ([@felthy](https://github.com/felthy)) - -#### Committers: 2 - -- Dan Abramov ([gaearon](https://github.com/gaearon)) -- Piotr ([piotr-cz](https://github.com/piotr-cz)) - -### Migrating from 1.0.15 to 1.0.16 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save --save-exact react-scripts@1.0.16 -``` - -or - -```sh -yarn add --exact react-scripts@1.0.16 -``` - -## 1.0.15 (October 30, 2017) - -#### :bug: Bug Fix - -- `react-scripts` - - - [#3287](https://github.com/facebook/create-react-app/pull/3287) Fix favicon sizes value in the project manifest. ([@ryansully](https://github.com/ryansully)) - -- `react-dev-utils`, `react-scripts` - - - [#3230](https://github.com/facebook/create-react-app/pull/3230) Fix watching for changes in `src/node_modules`. ([@xjlim](https://github.com/xjlim)) - -#### :nail_care: Enhancement - -- `react-scripts` - - - [#3239](https://github.com/facebook/create-react-app/pull/3239) Allow importing `.mjs` files. ([@Timer](https://github.com/Timer)) - - [#3340](https://github.com/facebook/create-react-app/pull/3340) Polyfill `requestAnimationFrame` in test environment. ([@gaearon](https://github.com/gaearon)) - -- `babel-preset-react-app`, `react-dev-utils`, `react-error-overlay`, `react-scripts` - - - [#3342](https://github.com/facebook/create-react-app/pull/3342) Bump dependencies. ([@gaearon](https://github.com/gaearon)) - -- `react-dev-utils`, `react-error-overlay` - - - [#3100](https://github.com/facebook/create-react-app/pull/3100) Add click-to-open support for build errors. ([@tharakawj](https://github.com/tharakawj)) - -- `create-react-app` - - - [#3355](https://github.com/facebook/create-react-app/pull/3355) Add preflight CWD check for npm to detect bad Windows setups. ([@gaearon](https://github.com/gaearon)) - -#### :memo: Documentation - -- User Guide - - - [#2957](https://github.com/facebook/create-react-app/pull/2957) Use `npm-run-all` to build Sass and JS. ([@shime](https://github.com/shime)) - - [#3108](https://github.com/facebook/create-react-app/pull/3108) Update the Service Worker opt-out documentation. ([@captDaylight](https://github.com/captDaylight)) - - [#3286](https://github.com/facebook/create-react-app/pull/3286) Add documentation for Enzyme 3 integration. ([@ryansully](https://github.com/ryansully)) - - [#3328](https://github.com/facebook/create-react-app/pull/3328) Recommend react-snap as an alternative to react-snapshot. ([@aaronshaf](https://github.com/aaronshaf)) - - [#3279](https://github.com/facebook/create-react-app/pull/3279) Add jest coverage configuration docs. ([@mattphillips](https://github.com/mattphillips)) - - [#3303](https://github.com/facebook/create-react-app/pull/3303) Update link to Jest Expect docs. ([@jbranchaud](https://github.com/jbranchaud)) - - [#3289](https://github.com/facebook/create-react-app/pull/3289) Fix dead link to Jest "expect" docs. ([@alexkrolick](https://github.com/alexkrolick)) - - [#3265](https://github.com/facebook/create-react-app/pull/3265) Add external links to deployment services. ([@aericson](https://github.com/aericson)) - - [#3075](https://github.com/facebook/create-react-app/pull/3075) Minor docs change to highlight dev proxy behaviour. ([@davidjb](https://github.com/davidjb)) - - [#3185](https://github.com/facebook/create-react-app/pull/3185) Correct manual proxy documentation. ([@robertpanzer](https://github.com/robertpanzer)) - -- README - - - [#3227](https://github.com/facebook/create-react-app/pull/3227) Fix package management link in README for issue #3218. ([@nishina555](https://github.com/nishina555)) - - [#3211](https://github.com/facebook/create-react-app/pull/3211) Improve grammar in README. ([@Mohamed3on](https://github.com/Mohamed3on)) - -#### :house: Internal - -- Other - - - [#3345](https://github.com/facebook/create-react-app/pull/3345) Stop using `npm link` in tests. ([@Timer](https://github.com/Timer)) - -- `react-error-overlay` - - - [#3122](https://github.com/facebook/create-react-app/pull/3122) Fix for add .gitattributes file #3080. ([@ijajmulani](https://github.com/ijajmulani)) - - [#3267](https://github.com/facebook/create-react-app/pull/3267) Use production React version for bundled overlay. ([@Timer](https://github.com/Timer)) - - [#3264](https://github.com/facebook/create-react-app/pull/3264) Add warning when using `react-error-overlay` in production. ([@Timer](https://github.com/Timer)) - - [#3263](https://github.com/facebook/create-react-app/pull/3263) `react-error-overlay` has no dependencies now (it's bundled). ([@Timer](https://github.com/Timer)) - - [#3142](https://github.com/facebook/create-react-app/pull/3142) Make error overlay run in the context of the iframe. ([@tharakawj](https://github.com/tharakawj)) - -- `react-scripts` - - - [#3150](https://github.com/facebook/create-react-app/pull/3150) Remove an useless negation in `registerServiceWorker.js`. ([@dunglas](https://github.com/dunglas)) - - [#3158](https://github.com/facebook/create-react-app/pull/3158) Remove `output.path` from dev webpack config. ([@nikolas](https://github.com/nikolas)) - - [#3281](https://github.com/facebook/create-react-app/pull/3281) Add a workaround for Uglify incompatibility with Safari 10.0 in the future. ([@satyavh](https://github.com/satyavh)) - - [#3146](https://github.com/facebook/create-react-app/pull/3146) Fix `reason-react` support. ([@lpalmes](https://github.com/lpalmes)) - - [#3236](https://github.com/facebook/create-react-app/pull/3236) Update `style-loader` and disable inclusion of its HMR code in builds. ([@insin](https://github.com/insin)) - - [#3246](https://github.com/facebook/create-react-app/pull/3246) Update `url-loader` to 0.6.2 for mime ReDoS vulnerability. ([@d3viant0ne](https://github.com/d3viant0ne)) - - [#2914](https://github.com/facebook/create-react-app/pull/2914) `` -> ``. ([@Hurtak](https://github.com/Hurtak)) - -#### Committers: 24 - -- Aaron Shafovaloff ([aaronshaf](https://github.com/aaronshaf)) -- Alex ([alexkrolick](https://github.com/alexkrolick)) -- André Ericson ([aericson](https://github.com/aericson)) -- Dan Abramov ([gaearon](https://github.com/gaearon)) -- David Beitey ([davidjb](https://github.com/davidjb)) -- Hrvoje Šimić ([shime](https://github.com/shime)) -- IJAJ MULANI ([ijajmulani](https://github.com/ijajmulani)) -- Joe Haddad ([Timer](https://github.com/Timer)) -- Joe Lim ([xjlim](https://github.com/xjlim)) -- Jonny Buchanan ([insin](https://github.com/insin)) -- Josh Branchaud ([jbranchaud](https://github.com/jbranchaud)) -- Joshua Wiens ([d3viant0ne](https://github.com/d3viant0ne)) -- Kévin Dunglas ([dunglas](https://github.com/dunglas)) -- Lorenzo Palmes ([lpalmes](https://github.com/lpalmes)) -- Matt Phillips ([mattphillips](https://github.com/mattphillips)) -- Mohamed Oun ([Mohamed3on](https://github.com/Mohamed3on)) -- Nik Nyby ([nikolas](https://github.com/nikolas)) -- Petr Huřťák ([Hurtak](https://github.com/Hurtak)) -- Robert Panzer ([robertpanzer](https://github.com/robertpanzer)) -- Ryan Sullivan ([ryansully](https://github.com/ryansully)) -- Satya van Heummen ([satyavh](https://github.com/satyavh)) -- Tharaka Wijebandara ([tharakawj](https://github.com/tharakawj)) -- Toshiharu Nishina ([nishina555](https://github.com/nishina555)) -- [captDaylight](https://github.com/captDaylight) - -### Migrating from 1.0.14 to 1.0.15 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save --save-exact react-scripts@1.0.15 -``` - -or - -```sh -yarn add --exact react-scripts@1.0.15 -``` - -## 1.0.14 (September 26, 2017) - -#### :bug: Bug Fix - -- `react-dev-utils` - - - [#3098](https://github.com/facebook/create-react-app/pull/3098) Always reload the page on next compile after a runtime error. ([@Timer](https://github.com/Timer)) - -- `react-error-overlay` - - - [#3079](https://github.com/facebook/create-react-app/pull/3079) Fix code context on Windows. ([@Timer](https://github.com/Timer)) - -#### :nail_care: Enhancement - -- `react-dev-utils` - - - [#3077](https://github.com/facebook/create-react-app/pull/3077) Auto-detect running editor on Linux for error overlay. ([@gulderov](https://github.com/gulderov)) - - - [#3131](https://github.com/facebook/create-react-app/pull/3131) Display process pid in already running message. ([@Pajn](https://github.com/Pajn)) - -#### :memo: Documentation - -- Other - - - [#3163](https://github.com/facebook/create-react-app/pull/3163) Add link to active CSS modules discussion. ([@NeekSandhu](https://github.com/NeekSandhu)) - -- `react-scripts` - - - [#2908](https://github.com/facebook/create-react-app/pull/2908) Note that class fields have progressed to stage 3. ([@rickbeerendonk](https://github.com/rickbeerendonk)) - - - [#3160](https://github.com/facebook/create-react-app/pull/3160) Update unclear wording in webpack configuration (file loader section). ([@kristiehoward](https://github.com/kristiehoward)) - -- `eslint-config-react-app` - - - [#3072](https://github.com/facebook/create-react-app/pull/3072) Update eslint versions for install instructions. ([@jdcrensh](https://github.com/jdcrensh)) - -#### :house: Internal - -- `react-scripts` - - - [#3157](https://github.com/facebook/create-react-app/pull/3157) Update `webpack-dev-server` to `2.8.2`. ([@nikolas](https://github.com/nikolas)) - - - [#2989](https://github.com/facebook/create-react-app/pull/2989) Update install template to match accessibility guidelines. ([@davidleger95](https://github.com/davidleger95)) - -- `react-error-overlay` - - - [#3065](https://github.com/facebook/create-react-app/pull/3065) Updated `react-error-overlay` to latest Flow (`0.54.0`). ([@duvet86](https://github.com/duvet86)) - - - [#3102](https://github.com/facebook/create-react-app/pull/3102) Clean target directory before compiling overlay. ([@Timer](https://github.com/Timer)) - -- `create-react-app`, `react-dev-utils`, `react-error-overlay`, `react-scripts` - - - [#3058](https://github.com/facebook/create-react-app/pull/3058) Re-run prettier for all files and pin the version. ([@viankakrisna](https://github.com/viankakrisna)) - - - [#3107](https://github.com/facebook/create-react-app/pull/3107) Run CI on `npm@^4`. ([@viankakrisna](https://github.com/viankakrisna)) - -#### Committers: 12 - -- Ade Viankakrisna Fadlil ([viankakrisna](https://github.com/viankakrisna)) -- David Leger ([davidleger95](https://github.com/davidleger95)) -- Joe Haddad ([Timer](https://github.com/Timer)) -- Jon Crenshaw ([jdcrensh](https://github.com/jdcrensh)) -- Kristie Howard ([kristiehoward](https://github.com/kristiehoward)) -- Luca ([duvet86](https://github.com/duvet86)) -- Neek Sandhu ([NeekSandhu](https://github.com/NeekSandhu)) -- Nik Nyby ([nikolas](https://github.com/nikolas)) -- Rasmus Eneman ([Pajn](https://github.com/Pajn)) -- Rick Beerendonk ([rickbeerendonk](https://github.com/rickbeerendonk)) -- Sophie Alpert ([sophiebits](https://github.com/sophiebits)) -- [gulderov](https://github.com/gulderov) - -### Migrating from 1.0.13 to 1.0.14 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save --save-exact react-scripts@1.0.14 -``` - -or - -```sh -yarn add --exact react-scripts@1.0.14 -``` - -## 1.0.13 (September 2, 2017) - -#### :bug: Bug Fix - -- `react-error-overlay` - - - [#3051](https://github.com/facebook/create-react-app/pull/3051) Fix case-sensitivity issue with upgrading the package version. ([@tharakawj](https://github.com/tharakawj)) - -- `react-dev-utils` - - - [#3049](https://github.com/facebook/create-react-app/pull/3049) Print filesize difference for chunks. ([@esturcke](https://github.com/esturcke)) - -- `react-scripts` - - - [#3046](https://github.com/facebook/create-react-app/pull/3046) Fix crash in development mode on IE11. ([@tharakawj](https://github.com/tharakawj)) - -#### :nail_care: Enhancement - -- `react-scripts` - - - [#3033](https://github.com/facebook/create-react-app/pull/3033) Add an empty mock for `child_process` to let some libraries compile. ([@McFlurriez](https://github.com/McFlurriez)) - -#### :house: Internal - -- `react-dev-utils`, `react-error-overlay` - - - [#3028](https://github.com/facebook/create-react-app/pull/3028) Make error overlay filename configurable. ([@jaredpalmer](https://github.com/jaredpalmer)) - -#### Committers: 4 - -- Anthony ([McFlurriez](https://github.com/McFlurriez)) -- Erik J. Sturcke ([esturcke](https://github.com/esturcke)) -- Jared Palmer ([jaredpalmer](https://github.com/jaredpalmer)) -- Tharaka Wijebandara ([tharakawj](https://github.com/tharakawj)) - -### Migrating from 1.0.12 to 1.0.13 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save --save-exact react-scripts@1.0.13 -``` - -or - -```sh -yarn add --exact react-scripts@1.0.13 -``` - -## 1.0.12 (August 28, 2017) - -#### :bug: Bug Fix - -- `react-error-overlay` - - - [#3012](https://github.com/facebook/create-react-app/pull/3012) Fix module function name in error overlay. ([@gaearon](https://github.com/gaearon)) - -- `react-dev-utils` - - [#2938](https://github.com/facebook/create-react-app/pull/2938) Remove superfluous lodash usage. ([@Timer](https://github.com/Timer)) - -#### :nail_care: Enhancement - -- `react-scripts` - - - [#2917](https://github.com/facebook/create-react-app/pull/2917) Optimize the size of default favicon. ([@sylvainbaronnet](https://github.com/sylvainbaronnet)) - -#### :memo: Documentation - -- `react-scripts` - - - [#2986](https://github.com/facebook/create-react-app/pull/2986) Docs: debugging in WebStorm. ([@prigara](https://github.com/prigara)) - - [#2948](https://github.com/facebook/create-react-app/pull/2948) Remove Modulus from user guide. ([@Zertz](https://github.com/Zertz)) - - [#2927](https://github.com/facebook/create-react-app/pull/2927) Update README.md. ([@tbassetto](https://github.com/tbassetto)) - -- `react-dev-utils` - - - [#2942](https://github.com/facebook/create-react-app/pull/2942) Fix docs for `printFileSizesAfterBuild`. ([@Kerumen](https://github.com/Kerumen)) - -#### :house: Internal - -- `react-error-overlay`, `react-scripts` - - - [#2991](https://github.com/facebook/create-react-app/pull/2991) Update `babel-runtime` dependency ([@christophehurpeau](https://github.com/christophehurpeau)) - -- `react-dev-utils`, `react-error-overlay`, `react-scripts` - - - [#2515](https://github.com/facebook/create-react-app/pull/2515) Convert `react-error-overlay` to React ([@tharakawj](https://github.com/tharakawj)) - -#### Committers: 9 - -- Christophe Hurpeau ([christophehurpeau](https://github.com/christophehurpeau)) -- Dan Abramov ([gaearon](https://github.com/gaearon)) -- Ekaterina Prigara ([prigara](https://github.com/prigara)) -- Joe Haddad ([Timer](https://github.com/Timer)) -- Pier-Luc Gendreau ([Zertz](https://github.com/Zertz)) -- Sylvain Baronnet ([sylvainbaronnet](https://github.com/sylvainbaronnet)) -- Tharaka Wijebandara ([tharakawj](https://github.com/tharakawj)) -- Thomas Bassetto ([tbassetto](https://github.com/tbassetto)) -- Yann Pringault ([Kerumen](https://github.com/Kerumen)) - -### Migrating from 1.0.11 to 1.0.12 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save --save-exact react-scripts@1.0.12 -``` - -or - -```sh -yarn add --exact react-scripts@1.0.12 -``` - -**Note:** there’s a [known issue](https://github.com/facebook/create-react-app/issues/3041) that might cause the project to not compile after upgrading. In this case, migrate straight to `1.0.13` which doesn’t have this issue. - -## 1.0.11 (August 9, 2017) - -#### :bug: Bug Fix - -- `create-react-app` - - - [#2884](https://github.com/facebook/create-react-app/pull/2884) Improve offline heuristic for proxied environments. ([@bsyk](https://github.com/bsyk)) - - When a Yarn proxy is set, we will check its connectivity if we cannot reach Yarn's registry. This is often the case when DNS lookups must be made through the proxy. - - - [#2853](https://github.com/facebook/create-react-app/pull/2853) Allow use of scoped packages with a pinned version. ([@wileybenet](https://github.com/wileybenet)) - -- `react-dev-utils` - - - [#2796](https://github.com/facebook/create-react-app/pull/2796) Properly escape HTML tags in error overlay. ([@ccloli](https://github.com/ccloli)) - - Elements printed in their entirety would sometimes render as HTML. This should no longer happen and should properly render as text. - -- `react-dev-utils`, `react-scripts` - - [#2834](https://github.com/facebook/create-react-app/pull/2834) Make `formatWebpackMessages` return all messages ([@onigoetz](https://github.com/onigoetz)) -- `react-scripts` - - [#2806](https://github.com/facebook/create-react-app/pull/2806) Fix SockJS version compatibility. ([@christianbundy](https://github.com/christianbundy)) - - [#2738](https://github.com/facebook/create-react-app/pull/2738) Fix Jest `node` file resolution. ([@mostafah](https://github.com/mostafah)) - -#### :nail_care: Enhancement - -- `react-scripts` - - - [#2818](https://github.com/facebook/create-react-app/pull/2818) Allow sourcemaps to be disabled. ([@viankakrisna](https://github.com/viankakrisna)) - - As applications grow more complex, it is possible webpack may run out of memory while generating source maps. They may now be disabled by setting `GENERATE_SOURCEMAP=false`. - - - [#2913](https://github.com/facebook/create-react-app/pull/2913) Allow flags to be passed to node when running `react-scripts`. ([@koistya](https://github.com/koistya)) - - [#2574](https://github.com/facebook/create-react-app/pull/2574) Upgrade to `webpack@3`. ([@themre](https://github.com/themre)) - - [#2747](https://github.com/facebook/create-react-app/pull/2747) Simplify webpack configuration using `Rule.oneOf`. ([@Furizaa](https://github.com/Furizaa)) - -- `react-dev-utils`, `react-scripts` - - [#2468](https://github.com/facebook/create-react-app/pull/2468) Allow importing `package.json`. ([@iamdoron](https://github.com/iamdoron)) - - [#2650](https://github.com/facebook/create-react-app/pull/2650) Make UglifyJS error friendlier. ([@viankakrisna](https://github.com/viankakrisna)) -- `create-react-app` - - [#2785](https://github.com/facebook/create-react-app/pull/2785) Change error wording and list conflicting files when initializing app. ([@OwenFlood](https://github.com/OwenFlood)) -- `react-dev-utils` - - [#2761](https://github.com/facebook/create-react-app/pull/2761) Don't prompt to install serve if already installed. ([@OwenFlood](https://github.com/OwenFlood)) - - [#2754](https://github.com/facebook/create-react-app/pull/2754) Auto-detect JetBrains IDEs. ([@danrr](https://github.com/danrr)) - - [#2740](https://github.com/facebook/create-react-app/pull/2740) Support PyCharm in `launchEditor`. ([@danrr](https://github.com/danrr)) - - [#2723](https://github.com/facebook/create-react-app/pull/2723) Reorder vim arguments in `launchEditor` so `--remote` works. ([@trygveaa](https://github.com/trygveaa)) -- `eslint-config-react-app`, `react-scripts` - - [#2735](https://github.com/facebook/create-react-app/pull/2735) Upgrade to `eslint@4`. ([@trungdq88](https://github.com/trungdq88)) -- `eslint-config-react-app` - - [#2701](https://github.com/facebook/create-react-app/pull/2701) Set `allowTaggedTemplates` to true (eslint). ([@denkristoffer](https://github.com/denkristoffer)) - -#### :memo: Documentation - -- Other - - [#2728](https://github.com/facebook/create-react-app/pull/2728) Add Electrode to alternatives. ([@animesh10](https://github.com/animesh10)) - - [#2788](https://github.com/facebook/create-react-app/pull/2788) Update link for motion. ([@viankakrisna](https://github.com/viankakrisna)) - - [#2697](https://github.com/facebook/create-react-app/pull/2697) Fix env list ordering. ([@alexeyraspopov](https://github.com/alexeyraspopov)) -- `react-dev-utils` - - [#2798](https://github.com/facebook/create-react-app/pull/2798) Update note about `webpackHotDevClient` support. ([@ForbesLindesay](https://github.com/ForbesLindesay)) -- `react-scripts` - - [#2822](https://github.com/facebook/create-react-app/pull/2822) Add explicit "Opting Out of Caching" header. ([@gaearon](https://github.com/gaearon)) - - [#2725](https://github.com/facebook/create-react-app/pull/2725) Fixed typo. ([@zeel](https://github.com/zeel)) - - [#2668](https://github.com/facebook/create-react-app/pull/2668) Document `basename` feature in `react-router`. ([@viankakrisna](https://github.com/viankakrisna)) - - [#2719](https://github.com/facebook/create-react-app/pull/2719) Remove Windows note for `source-map-explorer`. ([@hodanny](https://github.com/hodanny)) -- `babel-preset-react-app` - - [#2732](https://github.com/facebook/create-react-app/pull/2732) Update link to issue blocking JSX hoisting. ([@ForbesLindesay](https://github.com/ForbesLindesay)) - -#### :house: Internal - -- `create-react-app`, `eslint-config-react-app`, `react-dev-utils`, `react-error-overlay`, `react-scripts` - - [#2923](https://github.com/facebook/create-react-app/pull/2923) Update deps. ([@Timer](https://github.com/Timer)) -- `eslint-config-react-app` - - [#2718](https://github.com/facebook/create-react-app/pull/2718) Re-enable flowtype warning. ([@oskarkook](https://github.com/oskarkook)) -- Other - - [#2700](https://github.com/facebook/create-react-app/pull/2700) Unstage `yarn.lock` pre-commit. ([@jdcrensh](https://github.com/jdcrensh)) -- `react-scripts` - - [#2873](https://github.com/facebook/create-react-app/pull/2873) Use template strings. ([@monkindey](https://github.com/monkindey)) - -#### Committers: 26 - -- 864907600cc ([ccloli](https://github.com/ccloli)) -- Ade Viankakrisna Fadlil ([viankakrisna](https://github.com/viankakrisna)) -- Alexey Raspopov ([alexeyraspopov](https://github.com/alexeyraspopov)) -- Andreas Hoffmann ([Furizaa](https://github.com/Furizaa)) -- Animesh Dutta ([animesh10](https://github.com/animesh10)) -- Ben Sykes ([bsyk](https://github.com/bsyk)) -- Christian Bundy ([christianbundy](https://github.com/christianbundy)) -- Dan Abramov ([gaearon](https://github.com/gaearon)) -- Dan Ristea ([danrr](https://github.com/danrr)) -- Danny Ho ([hodanny](https://github.com/hodanny)) -- Forbes Lindesay ([ForbesLindesay](https://github.com/ForbesLindesay)) -- Joe Haddad ([Timer](https://github.com/Timer)) -- Jon Crenshaw ([jdcrensh](https://github.com/jdcrensh)) -- Kiho · Cham ([monkindey](https://github.com/monkindey)) -- Konstantin Tarkus ([koistya](https://github.com/koistya)) -- Kristoffer ([denkristoffer](https://github.com/denkristoffer)) -- Mostafa Hajizadeh ([mostafah](https://github.com/mostafah)) -- Oskar Köök ([oskarkook](https://github.com/oskarkook)) -- Owen Flood ([OwenFlood](https://github.com/OwenFlood)) -- Stéphane Goetz ([onigoetz](https://github.com/onigoetz)) -- Trygve Aaberge ([trygveaa](https://github.com/trygveaa)) -- Wiley Bennett ([wileybenet](https://github.com/wileybenet)) -- [iamdoron](https://github.com/iamdoron) -- [themre](https://github.com/themre) -- zeel ([zeel](https://github.com/zeel)) -- Đinh Quang Trung ([trungdq88](https://github.com/trungdq88)) - -### Migrating from 1.0.10 to 1.0.11 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save --save-exact react-scripts@1.0.11 -``` - -or - -```sh -yarn add --exact react-scripts@1.0.11 -``` - -## 1.0.10 (June 29, 2017) - -#### :bug: Bug Fix - -- `react-dev-utils` - - - [#2692](https://github.com/facebook/create-react-app/pull/2692) Fix IE11 crash in development. ([@pdhoopr](https://github.com/pdhoopr)) - -- `create-react-app` - - [#2683](https://github.com/facebook/create-react-app/pull/2683) Fix a typo. ([@BenBrostoff](https://github.com/BenBrostoff)) - -#### :memo: Documentation - -- README - - - [#2402](https://github.com/facebook/create-react-app/pull/2402) Added `gluestick` to the alternatives section. ([@JoeCortopassi](https://github.com/JoeCortopassi)) - -#### Committers: 5 - -- Ben Brostoff ([BenBrostoff](https://github.com/BenBrostoff)) -- Forbes Lindesay ([ForbesLindesay](https://github.com/ForbesLindesay)) -- Joe Haddad ([Timer](https://github.com/Timer)) -- Patrick Hooper ([pdhoopr](https://github.com/pdhoopr)) -- [JoeCortopassi](https://github.com/JoeCortopassi) - -### Migrating from 1.0.9 to 1.0.10 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save --save-exact react-scripts@1.0.10 -``` - -or - -```sh -yarn add --exact react-scripts@1.0.10 -``` - -## 1.0.9 (June 29, 2017) - -#### :bug: Bug Fix - -- `react-scripts` - - - [#2680](https://github.com/facebook/create-react-app/pull/2680) Fix external CSS imports. ([@gaearon](https://github.com/gaearon)) - -#### :memo: Documentation - -- `react-scripts` - - - [#2679](https://github.com/facebook/create-react-app/pull/2679) Fix minor typo. ([@dbanck](https://github.com/dbanck)) - - [#2666](https://github.com/facebook/create-react-app/pull/2666) Add more info about Apache client side routing. ([@viankakrisna](https://github.com/viankakrisna)) - - [#2671](https://github.com/facebook/create-react-app/pull/2671) Add JSON and CSS to Prettier instructions. ([@jbovenschen](https://github.com/jbovenschen)) - -#### :house: Internal - -- Other - - - [#2673](https://github.com/facebook/create-react-app/pull/2673) Bootstrap with Yarn. ([@Timer](https://github.com/Timer)) - - [#2659](https://github.com/facebook/create-react-app/pull/2659) Test Node 8 on Travis. ([@gaearon](https://github.com/gaearon)) - -#### Committers: 5 - -- Ade Viankakrisna Fadlil ([viankakrisna](https://github.com/viankakrisna)) -- Dan Abramov ([gaearon](https://github.com/gaearon)) -- Daniel Banck ([dbanck](https://github.com/dbanck)) -- Jaco Bovenschen ([jbovenschen](https://github.com/jbovenschen)) -- Joe Haddad ([Timer](https://github.com/Timer)) - -### Migrating from 1.0.8 to 1.0.9 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save --save-exact react-scripts@1.0.9 -``` - -or - -```sh -yarn add --exact react-scripts@1.0.9 -``` - -## 1.0.8 (June 28, 2017) - -#### :bug: Bug Fix - -- `react-scripts` - - - [#2550](https://github.com/facebook/create-react-app/pull/2550) Fix Node 8 compatibility. ([@josephfrazier](https://github.com/josephfrazier)) - - [#2610](https://github.com/facebook/create-react-app/pull/2610) Fix sourcemap directory organization on Windows. ([@plusCubed](https://github.com/plusCubed)) - - [#2596](https://github.com/facebook/create-react-app/pull/2596) Fix an issue with minifying emojis. ([@viankakrisna](https://github.com/viankakrisna)) - - [#2501](https://github.com/facebook/create-react-app/pull/2501) Fix incorrect check if `CI` variable is set to true. ([@varnav](https://github.com/varnav)) - - [#2432](https://github.com/facebook/create-react-app/pull/2432) In new projects, don't register service worker for projects using `PUBLIC_URL` for CDN. ([@jeffposnick](https://github.com/jeffposnick)) - - [#2470](https://github.com/facebook/create-react-app/pull/2470) In new projects, prioritize `index.css` over `App.css`. ([@bryankang](https://github.com/bryankang)) - -- `react-dev-utils` - - - [#2405](https://github.com/facebook/create-react-app/pull/2405) Fix detection of parent directory in `ModuleScopePlugin`. ([@diligiant](https://github.com/diligiant)) - - [#2562](https://github.com/facebook/create-react-app/pull/2562) Fix eject command output. ([@paweljedrzejczyk](https://github.com/paweljedrzejczyk)) - -#### :nail_care: Enhancement - -- `react-scripts` - - - [#2648](https://github.com/facebook/create-react-app/pull/2648) Warn about large bundle sizes. ([@gaearon](https://github.com/gaearon)) - - [#2511](https://github.com/facebook/create-react-app/pull/2511) Support `.web.js` extension for React Native Web. ([@mini-eggs](https://github.com/mini-eggs)) - - [#2645](https://github.com/facebook/create-react-app/pull/2645) Hide confusing "Skipping static resource" message. ([@gaearon](https://github.com/gaearon)) - - [#2389](https://github.com/facebook/create-react-app/pull/2389) Silence unnecessary warning from Babel. ([@gaearon](https://github.com/gaearon)) - - [#2429](https://github.com/facebook/create-react-app/pull/2429) Update `sw-precache-webpack-plugin` to lastest version. ([@goldhand](https://github.com/goldhand)) - - [#2600](https://github.com/facebook/create-react-app/pull/2600) Add empty mock for `dgram` Node module. ([@micopiira](https://github.com/micopiira)) - - [#2458](https://github.com/facebook/create-react-app/pull/2458) Add names to module factories in development. ([@Zaccc123](https://github.com/Zaccc123)) - - [#2551](https://github.com/facebook/create-react-app/pull/2551) In new projects, unregister service worker and force reload if `service-worker.js` is not found. ([@ro-savage](https://github.com/ro-savage)) - -- `babel-preset-react-app`, `react-dev-utils`, `react-scripts` - - - [#2658](https://github.com/facebook/create-react-app/pull/2658) Bump dependencies. ([@gaearon](https://github.com/gaearon)) - -- `create-react-app`, `react-scripts` - - - [#2657](https://github.com/facebook/create-react-app/pull/2657) Put `react-scripts` in `dependencies`, not `devDependencies`. ([@gaearon](https://github.com/gaearon)) - - [#2635](https://github.com/facebook/create-react-app/pull/2635) Silence unhelpful npm warnings. ([@gaearon](https://github.com/gaearon)) - -- `react-dev-utils` - - - [#2637](https://github.com/facebook/create-react-app/pull/2637) Auto-detect Brackets editor from error overlay. ([@petetnt](https://github.com/petetnt)) - - [#2552](https://github.com/facebook/create-react-app/pull/2552) Auto-detect running editor on Windows for error overlay. ([@levrik](https://github.com/levrik)) - - [#2622](https://github.com/facebook/create-react-app/pull/2622) Support opening PhpStorm for error overlay. ([@miraage](https://github.com/miraage)) - - [#2414](https://github.com/facebook/create-react-app/pull/2414) Support opening WebStorm 2017+ from error overlay. ([@wirmar](https://github.com/wirmar)) - - [#2518](https://github.com/facebook/create-react-app/pull/2518) Warn when trying to run on port below 1024 without admin permissions under Linux/macOS. ([@levrik](https://github.com/levrik)) - - [#2385](https://github.com/facebook/create-react-app/pull/2385) Suggest just `yarn build` in output. ([@gaearon](https://github.com/gaearon)) - -- `create-react-app` - - - [#1945](https://github.com/facebook/create-react-app/pull/1945) Fix grammar in CLI output. ([@ColinEberhardt](https://github.com/ColinEberhardt)) - -#### :memo: Documentation - -- User Guide - - - [#2662](https://github.com/facebook/create-react-app/pull/2662) Local testing docker links. ([@EnoahNetzach](https://github.com/EnoahNetzach)) - - [#2660](https://github.com/facebook/create-react-app/pull/2660) Minor code style edits to user guide. ([@gaearon](https://github.com/gaearon)) - - [#2656](https://github.com/facebook/create-react-app/pull/2656) Don't ask to install webpack for using Styleguidist. ([@gaearon](https://github.com/gaearon)) - - [#1641](https://github.com/facebook/create-react-app/pull/1641) Add instructions to use `source-map-explorer`. ([@gr33nfury](https://github.com/gr33nfury)) - - [#2044](https://github.com/facebook/create-react-app/pull/2044) Add React Styleguidist. ([@sapegin](https://github.com/sapegin)) - - [#2006](https://github.com/facebook/create-react-app/pull/2006) Added instruction on how to install Prettier. ([@MrHus](https://github.com/MrHus)) - - [#1813](https://github.com/facebook/create-react-app/pull/1813) Fix grammar. ([@iheng](https://github.com/iheng)) - - [#2060](https://github.com/facebook/create-react-app/pull/2060) Add more info about OOM build failiure [docs]. ([@GAumala](https://github.com/GAumala)) - - [#2305](https://github.com/facebook/create-react-app/pull/2305) Update docs with WebSocket proxy information. ([@jamesblight](https://github.com/jamesblight)) - - [#2445](https://github.com/facebook/create-react-app/pull/2445) Document `REACT_EDITOR` environment variable. ([@wirmar](https://github.com/wirmar)) - - [#2362](https://github.com/facebook/create-react-app/pull/2362) Add yarn example under "Installing a Dependency". ([@BrianDGLS](https://github.com/BrianDGLS)) - - [#2423](https://github.com/facebook/create-react-app/pull/2423) Add docs for setting up CircleCI for CRA. ([@knowbody](https://github.com/knowbody)) - - [#2427](https://github.com/facebook/create-react-app/pull/2427) Added link to tutorial on code splitting. ([@jayair](https://github.com/jayair)) - - [#2447](https://github.com/facebook/create-react-app/pull/2447) Fix wrong comment on Proxy guide. ([@hellowin](https://github.com/hellowin)) - - [#2538](https://github.com/facebook/create-react-app/pull/2538) Fix broken link to a tutorial. ([@romanyanke](https://github.com/romanyanke)) - - [#2522](https://github.com/facebook/create-react-app/pull/2522) Flow init to run as command not flag. ([@khanglu](https://github.com/khanglu)) - - [#2521](https://github.com/facebook/create-react-app/pull/2521) Fix broken link to Storybook docs. ([@shilman](https://github.com/shilman)) - - [#2500](https://github.com/facebook/create-react-app/pull/2500) Fix minor typo. ([@AlexxNica](https://github.com/AlexxNica)) - - [#2331](https://github.com/facebook/create-react-app/pull/2331) Re-add storybook && update the documentation and links. ([@ndelangen](https://github.com/ndelangen)) - - [#2454](https://github.com/facebook/create-react-app/pull/2454) Update Travis CI Node versions in User Guide. ([@ryansully](https://github.com/ryansully)) - - [#2420](https://github.com/facebook/create-react-app/pull/2420) Fix typo. ([@ruskakimov](https://github.com/ruskakimov)) - - [#2392](https://github.com/facebook/create-react-app/pull/2392) Update `jest-enzyme` section. ([@luftywiranda13](https://github.com/luftywiranda13)) - -- README - - - [#2517](https://github.com/facebook/create-react-app/pull/2517) Add Razzle to the alternatives. ([@kireerik](https://github.com/kireerik)) - - [#1931](https://github.com/facebook/create-react-app/pull/1931) Updated README. ([@shaunwallace](https://github.com/shaunwallace)) - - [#2492](https://github.com/facebook/create-react-app/pull/2492) Update webpack links to point to webpack 2. ([@laruiss](https://github.com/laruiss)) - -#### :house: Internal - -- Other - - - [#2465](https://github.com/facebook/create-react-app/pull/2465) Update Prettier to v1. ([@ianschmitz](https://github.com/ianschmitz)) - - [#2489](https://github.com/facebook/create-react-app/pull/2489) chore(templates): Move GitHub templates to hidden .github folder. ([@glennreyes](https://github.com/glennreyes)) - - [#2400](https://github.com/facebook/create-react-app/pull/2400) Added cache clear to e2e scripts. ([@ro-savage](https://github.com/ro-savage)) - - [#2397](https://github.com/facebook/create-react-app/pull/2397) Fix command in e2e-kitchensink.sh cleanup. ([@ro-savage](https://github.com/ro-savage)) - - [#2388](https://github.com/facebook/create-react-app/pull/2388) Fix wrong path expansion in end-to-end test. ([@gaearon](https://github.com/gaearon)) - - [#2387](https://github.com/facebook/create-react-app/pull/2387) Catch "No tests found" during CI. ([@EnoahNetzach](https://github.com/EnoahNetzach)) - -- `react-scripts` - - - [#2408](https://github.com/facebook/create-react-app/pull/2408) E2E testing enhancements. ([@EnoahNetzach](https://github.com/EnoahNetzach)) - - [#2430](https://github.com/facebook/create-react-app/pull/2430) Remove an unnecessary webpack option. ([@andykenward](https://github.com/andykenward)) - -- `react-dev-utils` - - - [#2483](https://github.com/facebook/create-react-app/pull/2483) Remove a scoped package dependency. ([@Timer](https://github.com/Timer)) - -#### Committers: 46 - -- Ade Viankakrisna Fadlil ([viankakrisna](https://github.com/viankakrisna)) -- Alexandre Nicastro ([AlexxNica](https://github.com/AlexxNica)) -- Andi N. Dirgantara ([hellowin](https://github.com/hellowin)) -- Andy Kenward ([andykenward](https://github.com/andykenward)) -- Artem Sapegin ([sapegin](https://github.com/sapegin)) -- Ashton ([ashtonsix](https://github.com/ashtonsix)) -- Brian Douglas ([BrianDGLS](https://github.com/BrianDGLS)) -- Colin Eberhardt ([ColinEberhardt](https://github.com/ColinEberhardt)) -- Colin Galindo ([gr33nfury](https://github.com/gr33nfury)) -- Dan Abramov ([gaearon](https://github.com/gaearon)) -- Daniel Ciao ([plusCubed](https://github.com/plusCubed)) -- Erik Engi ([kireerik](https://github.com/kireerik)) -- Evan Jones ([mini-eggs](https://github.com/mini-eggs)) -- Fabrizio Castellarin ([EnoahNetzach](https://github.com/EnoahNetzach)) -- Frédéric Miserey ([diligiant](https://github.com/diligiant)) -- Gabriel Aumala ([GAumala](https://github.com/GAumala)) -- Glenn Reyes ([glennreyes](https://github.com/glennreyes)) -- Heng Li ([iheng](https://github.com/iheng)) -- Ian Schmitz ([ianschmitz](https://github.com/ianschmitz)) -- James Blight ([jamesblight](https://github.com/jamesblight)) -- Jay V ([jayair](https://github.com/jayair)) -- Jeffrey Posnick ([jeffposnick](https://github.com/jeffposnick)) -- Joe Haddad ([Timer](https://github.com/Timer)) -- Joseph Frazier ([josephfrazier](https://github.com/josephfrazier)) -- Khang Lu ([khanglu](https://github.com/khanglu)) -- Levin Rickert ([levrik](https://github.com/levrik)) -- Lufty Wiranda ([luftywiranda13](https://github.com/luftywiranda13)) -- Maarten Hus ([MrHus](https://github.com/MrHus)) -- Marius Wirtherle ([wirmar](https://github.com/wirmar)) -- Mateusz Zatorski ([knowbody](https://github.com/knowbody)) -- Michael Shilman ([shilman](https://github.com/shilman)) -- Mico Piira ([micopiira](https://github.com/micopiira)) -- Mikhail Osher ([miraage](https://github.com/miraage)) -- Norbert de Langen ([ndelangen](https://github.com/ndelangen)) -- Paweł Jędrzejczyk ([paweljedrzejczyk](https://github.com/paweljedrzejczyk)) -- Pete Nykänen ([petetnt](https://github.com/petetnt)) -- Ro Savage ([ro-savage](https://github.com/ro-savage)) -- Roman ([romanyanke](https://github.com/romanyanke)) -- Rustem Kakimov ([ruskakimov](https://github.com/ruskakimov)) -- Ryan Sullivan ([ryansully](https://github.com/ryansully)) -- Stanislas Ormières ([laruiss](https://github.com/laruiss)) -- Will Farley ([goldhand](https://github.com/goldhand)) -- Zac Kwan ([Zaccc123](https://github.com/Zaccc123)) -- [bryankang](https://github.com/bryankang) -- [varnav](https://github.com/varnav) -- shaun wallace ([shaunwallace](https://github.com/shaunwallace)) - -### Migrating from 1.0.7 to 1.0.8 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@1.0.8 -``` - -or - -```sh -yarn add --dev --exact react-scripts@1.0.8 -``` - -**If you previously used `HTTPS=true` environment variable in development**, make sure you aren't affected by a now-fixed vulnerability in webpack by [visiting this page](http://badcert.mike.works/). You can read more about the vulnerability [here](https://medium.com/@mikenorth/webpack-preact-cli-vulnerability-961572624c54). - -You may optionally then move `react-scripts` from `devDependencies` to `dependencies` since that’s how we’ll structure newly created projects. It is not necessary though. - -If you left the service worker integration enabled and didn’t change how it works, you can replace `src/registerServiceWorker.js` with [this updated version](https://raw.githubusercontent.com/facebook/create-react-app/895c475d3fc218c65dcac9a3ef3f2c0ea746a1ed/packages/react-scripts/template/src/registerServiceWorker.js). - -If you haven't changed the default CSS organization, you may want to apply [this fix](https://github.com/facebook/create-react-app/pull/2470/files) that makes `index.css` take precedence over `App.css` in your project. - -## 1.0.7 (May 27, 2017) - -#### :bug: Bug Fix - -- `react-scripts` - - - [#2382](https://github.com/facebook/create-react-app/pull/2382) Consistently set environment variables. ([@gaearon](https://github.com/gaearon)) - - [#2379](https://github.com/facebook/create-react-app/pull/2379) Temporarily disable `comparisons` feature in uglify compression. ([@davidascher](https://github.com/davidascher)) - -#### :nail_care: Enhancement - -- `react-scripts` - - - [#2383](https://github.com/facebook/create-react-app/pull/2383) Update webpack to 2.6.1. ([@gaearon](https://github.com/gaearon)) - - [#2349](https://github.com/facebook/create-react-app/pull/2349) Update webpack to v2.6.0. ([@ingro](https://github.com/ingro)) - - [#2351](https://github.com/facebook/create-react-app/pull/2351) Removed the overriding of `reduce_vars` since webpack v2.6.0 included fix of Uglify. ([@Zaccc123](https://github.com/Zaccc123)) - -- `react-dev-utils`, `react-scripts` - - - [#2361](https://github.com/facebook/create-react-app/pull/2361) Print file sizes with correct build folder path. ([@fezhengjin](https://github.com/fezhengjin)) - -#### :memo: Documentation - -- `react-scripts` - - - [#2372](https://github.com/facebook/create-react-app/pull/2372) Update README.md for `now` deployments. ([@purplecones](https://github.com/purplecones)) - - [#2350](https://github.com/facebook/create-react-app/pull/2350) Fix broken links. ([@gaearon](https://github.com/gaearon)) - -#### Committers: 6 - -- Dan Abramov ([gaearon](https://github.com/gaearon)) -- David Ascher ([davidascher](https://github.com/davidascher)) -- Emanuele Ingrosso ([ingro](https://github.com/ingro)) -- Jin Zheng ([fezhengjin](https://github.com/fezhengjin)) -- Mirza Joldic ([purplecones](https://github.com/purplecones)) -- Zac Kwan ([Zaccc123](https://github.com/Zaccc123)) - -### Migrating from 1.0.6 to 1.0.7 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@1.0.7 -``` - -or - -```sh -yarn add --dev --exact react-scripts@1.0.7 -``` - -## 1.0.6 (May 24, 2017) - -#### :bug: Bug Fix - -- `eslint-config-react-app`, `react-error-overlay`, `react-scripts` - - - [#2346](https://github.com/facebook/create-react-app/pull/2346) Resolve Flow errors in an ESLint plugin. ([@iainbeeston](https://github.com/iainbeeston)) - -- `react-dev-utils` - - - [#2332](https://github.com/facebook/create-react-app/pull/2332) Fix proxying issues with backends that don't support IPv6. ([@Timer](https://github.com/Timer)) - -#### :nail_care: Enhancement - -- `react-scripts` - - - [#2347](https://github.com/facebook/create-react-app/pull/2347) Don't precache `/__*` URLs to fix Firebase hosting. ([@ryansully](https://github.com/ryansully)) - -#### :memo: Documentation - -- README - - - [#2334](https://github.com/facebook/create-react-app/pull/2334) Add missing files to the list. ([@jesselpalmer](https://github.com/jesselpalmer)) - -#### Committers: 4 - -- Iain Beeston ([iainbeeston](https://github.com/iainbeeston)) -- Jesse Palmer ([jesselpalmer](https://github.com/jesselpalmer)) -- Joe Haddad ([Timer](https://github.com/Timer)) -- Ryan Sullivan ([ryansully](https://github.com/ryansully)) - -### Migrating from 1.0.5 to 1.0.6 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@1.0.6 -``` - -or - -```sh -yarn add --dev --exact react-scripts@1.0.6 -``` - -## 1.0.5 (May 22, 2017) - -#### :bug: Bug Fix - -- `react-dev-utils`, `react-scripts` - - - [#2326](https://github.com/facebook/create-react-app/pull/2326) Files in `public/` folder should not be requested through proxy. ([@gaearon](https://github.com/gaearon)) - -#### :nail_care: Enhancement - -- `react-dev-utils` - - - [#2327](https://github.com/facebook/create-react-app/pull/2327) Limit console warnings to 5 files at most. ([@gaearon](https://github.com/gaearon)) - -- `eslint-config-react-app` - - - [#2325](https://github.com/facebook/create-react-app/pull/2325) Allow declaring variables before use in a scope above. ([@gaearon](https://github.com/gaearon)) - -#### :house: Internal - -- `react-dev-utils`, `react-scripts` - - - [#2320](https://github.com/facebook/create-react-app/pull/2320) Remove unnecessary dependencies. ([@pmadar](https://github.com/pmadar)) - -#### Committers: 2 - -- Dan Abramov ([gaearon](https://github.com/gaearon)) -- Pavol Madar ([pmadar](https://github.com/pmadar)) - -### Migrating from 1.0.4 to 1.0.5 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@1.0.5 -``` - -or - -```sh -yarn add --dev --exact react-scripts@1.0.5 -``` - -## 1.0.4 (May 22, 2017) - -#### :bug: Bug Fix - -- `react-error-overlay` - - Fix a regression in published package. - -### Migrating from 1.0.3 to 1.0.4 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@1.0.4 -``` - -or - -```sh -yarn add --dev --exact react-scripts@1.0.4 -``` - -## 1.0.3 (May 21, 2017) - -#### :bug: Bug Fix - -- `react-dev-utils` - - - [#2297](https://github.com/facebook/create-react-app/pull/2297) Don’t serve the development version from public IPs by default. ([@Timer](https://github.com/Timer)) - -- `eslint-config-react-app` - - - [#2311](https://github.com/facebook/create-react-app/pull/2311) Disable `flowtype/require-valid-file-annotation` lint rule due to false positives. ([@Robdel12](https://github.com/Robdel12)) - -- `react-dev-utils`, `react-error-overlay` - - - [#2301](https://github.com/facebook/create-react-app/pull/2301) Wrap more `console` calls into a check. ([@BrodaNoel](https://github.com/BrodaNoel)) - -- `react-scripts` - - [#2314](https://github.com/facebook/create-react-app/pull/2314) Fix a "File not found" false positive. ([@gaearon](https://github.com/gaearon)) - -#### Committers: 4 - -- Broda Noel ([BrodaNoel](https://github.com/BrodaNoel)) -- Dan Abramov ([gaearon](https://github.com/gaearon)) -- Joe Haddad ([Timer](https://github.com/Timer)) -- Robert DeLuca ([Robdel12](https://github.com/Robdel12)) - -### Migrating from 1.0.2 to 1.0.3 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@1.0.3 -``` - -or - -```sh -yarn add --dev --exact react-scripts@1.0.3 -``` - -## 1.0.2 (May 20, 2017) - -#### :bug: Bug Fix - -- `react-dev-utils`, `react-scripts` - - - [#2276](https://github.com/facebook/create-react-app/pull/2276) Serve a no-op service worker in development to ensure it doesn't cache the production build even if it was served on the same port. ([@jeffposnick](https://github.com/jeffposnick)) - -- `react-dev-utils`, `react-error-overlay` - - - [#2290](https://github.com/facebook/create-react-app/pull/2290) Wrap console calls into a check for IE9. ([@gaearon](https://github.com/gaearon)) - -- `react-dev-utils` - - - [#2282](https://github.com/facebook/create-react-app/pull/2282) Add Windows Subsystem for Linux support to the error overlay. ([@noinkling](https://github.com/noinkling)) - - [#2269](https://github.com/facebook/create-react-app/pull/2269) Fix a missing package dependency. ([@GreenGremlin](https://github.com/GreenGremlin)) - -#### :nail_care: Enhancement - -- `react-scripts` - - - [#2221](https://github.com/facebook/create-react-app/pull/2221) Ejecting should ensure you have clean `git status`. ([@milocosmopolitan](https://github.com/milocosmopolitan)) - - [#2288](https://github.com/facebook/create-react-app/pull/2288) Only enable host check if you use proxy, and add a way to opt out of it. ([@gaearon](https://github.com/gaearon)) - -#### :house: Internal - -- `react-dev-utils`, `react-scripts` - - - [#2283](https://github.com/facebook/create-react-app/pull/2283) Remove unnecessary dependencies(#751). ([@pmadar](https://github.com/pmadar)) - -#### Committers: 6 - -- Dan Abramov ([gaearon](https://github.com/gaearon)) -- Jeffrey Posnick ([jeffposnick](https://github.com/jeffposnick)) -- Jonathan ([GreenGremlin](https://github.com/GreenGremlin)) -- Malcolm ([noinkling](https://github.com/noinkling)) -- Milo Kang ([milocosmopolitan](https://github.com/milocosmopolitan)) -- [pmadar](https://github.com/pmadar) - -### Migrating from 1.0.1 to 1.0.2 - -Inside any created project that has not been ejected, run: - -```sh -npm install --save-dev --save-exact react-scripts@1.0.2 -``` - -or - -```sh -yarn add --dev --exact react-scripts@1.0.2 -``` - -If you previously had issues with an `Invalid Host Header` error, [follow these new instructions](https://github.com/facebook/create-react-app/blob/main/packages/react-scripts/template/README.md#invalid-host-header-errors-after-configuring-proxy) to fix it. - -## 1.0.1 (May 19, 2017) - -#### :bug: Bug Fix - -- `react-scripts` - - - [#2242](https://github.com/facebook/create-react-app/pull/2242) Fix `NODE_PATH=src` for `npm start` and `npm run build`. ([@ApacheEx](https://github.com/ApacheEx)) - - [#2261](https://github.com/facebook/create-react-app/pull/2261) Fix `NODE_PATH=src` for Jest. ([@gaearon](https://github.com/gaearon)) - - [#2255](https://github.com/facebook/create-react-app/pull/2255) Fix Windows path issue for generated service worker. ([@gaearon](https://github.com/gaearon)) - - [#2262](https://github.com/facebook/create-react-app/pull/2262) Additional fix to service worker config for `"homepage"` field. ([@gaearon](https://github.com/gaearon)) - - [#2250](https://github.com/facebook/create-react-app/pull/2250) Ignore `.env.local` in `test` environment. ([@gaearon](https://github.com/gaearon)) - - [#2246](https://github.com/facebook/create-react-app/pull/2246) Gracefully shut down the development server on signals. ([@gaearon](https://github.com/gaearon)) - -- `react-dev-utils` - - - [#2229](https://github.com/facebook/create-react-app/pull/2229) Show customized build path for serve instruction. ([@chyipin](https://github.com/chyipin)) - -- `react-dev-utils`, `react-error-overlay` - - - [#2243](https://github.com/facebook/create-react-app/pull/2243) Add missing package dependency. ([@Timer](https://github.com/Timer)) - -- `react-error-overlay` - - [#2238](https://github.com/facebook/create-react-app/pull/2238) Fix a crash when switching errors with arrow keys. ([@Gandem](https://github.com/Gandem)) - -#### :nail_care: Enhancement - -- `eslint-config-react-app` - - - [#2256](https://github.com/facebook/create-react-app/pull/2256) Turn off `operator-assignment` stylistic rule. ([@gaearon](https://github.com/gaearon)) - -- `react-scripts` - - [#2224](https://github.com/facebook/create-react-app/pull/2224) Add `

Bike rental in Nagercoil & Kanyakumari

Two-wheelers for hire by the hour, the day or the week, across Kanyakumari district.

Scooters and motorcycles

A two-wheeler is the easiest way to get around Nagercoil and the coast road, and to reach places a car cannot park. We hire scooters for everyday running and motorcycles for longer rides.

What it costs

Rates are quoted by the hour, the day or the week, and come down the longer you keep the bike. A refundable deposit applies, returned when the bike comes back. Fuel is on you.

What to bring

A valid two-wheeler driving licence and a government photo ID. Helmets are provided with every hire.

  • Hourly, daily and weekly rates
  • Helmets included
  • Pickup across Kanyakumari district
  • Refundable deposit, returned on return

Town by town

Where this works differently depending on where you are, we have written it up separately.

Need a bike?

Tell us your dates and where you are in Kanyakumari district, and we will come back with availability and the rate.

Where we deliver

Where we hire across Kanyakumari district

We serve the whole of Kanyakumari district in Tamil Nadu. Tell us where you are and we will sort out pickup.

Somewhere not listed? Ask us — if it is in the district, we can usually help. See every town we deliver to.

+ + diff --git a/bikes/kanyakumari/index.html b/bikes/kanyakumari/index.html new file mode 100644 index 00000000000..4661f7845de --- /dev/null +++ b/bikes/kanyakumari/index.html @@ -0,0 +1,90 @@ + + + + + + + + + + + + Bike & Scooty Rental in Kanyakumari for the Coast Road + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Bike and scooter rental in Kanyakumari

Kanyakumari is a town people explore rather than commute across, and a scooter is the best way to do it. The sights are spread along a few kilometres of coast, parking at each of them is easier on two wheels, and you can leave for sunrise without waiting for anybody.

The run most people make

Sunrise at the shore near the Vivekananda Rock, then breakfast, then north along the coast to Vattakottai fort — about six kilometres, and one of the better short rides in the district with the sea on your right the whole way. Sunset Point in the evening closes the loop. All of it is comfortable on a scooter and all of it involves parking somewhere that a car makes harder.

What the coast road is actually like

It is tarred and in fair condition, and it is windy — genuinely windy, most afternoons, because there is nothing between the road and the sea. A scooter is fine in it if you ride at an unhurried pace and hold the bars properly; a lightweight rider in a crosswind on an exposed stretch should take it slowly. Salt air is also why we check the vehicles over more often here than anywhere else in the district.

Delivered to where you are staying

Most people hiring here are staying a night or two, so we bring the vehicle to the hotel and collect it from there. Tell us the morning you want it and whether you are going out for sunrise, and it will be waiting with fuel in it rather than at a pump at half past five.

Questions about bike and scooter rental in Kanyakumari

Can I hire a scooter in Kanyakumari for sunrise?
Yes, and it is the most common reason people take one. Arrange it the evening before and we will have it at your hotel with fuel in it, so you are not looking for an open pump before dawn.
Is the coast road to Vattakottai safe on a two-wheeler?
It is a tarred road in fair condition and it is a popular ride. The thing to respect is the wind — there is nothing between the road and the sea, and it gets strong in the afternoon. Ride at an easy pace and it is a straightforward six kilometres.
Do you deliver bikes to hotels in Kanyakumari?
Yes. Give us the hotel and a time and we bring it there, and collect from the same place when you are finished. Kanyakumari is about twenty kilometres from our base, so a little notice helps.

How it works in Kanyakumari

  • Helmets with every vehicle — and worth wearing properly in the coastal wind
  • Hotel delivery and collection anywhere in Kanyakumari town
  • Hourly hire, which suits a sunrise run and a morning of sights
  • About 20 km from our base in Nagercoil, so say the night before where possible
Check availability in Kanyakumari

or call +91 63749 42976

+ + diff --git a/bikes/nagercoil/index.html b/bikes/nagercoil/index.html new file mode 100644 index 00000000000..7105568c407 --- /dev/null +++ b/bikes/nagercoil/index.html @@ -0,0 +1,90 @@ + + + + + + + + + + + + Bike & Scooty Rental in Nagercoil — By Hour, Day or Week + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Bike and scooter rental in Nagercoil

A two-wheeler is the quickest way to get around Nagercoil, and for a lot of what people need a vehicle for here — a day of errands, a run to the junction, getting to work while your own bike is in the shop — it is the sensible choice rather than the cheap one.

Why a two-wheeler makes sense in town

Traffic through the centre is heavy for most of the day and parking near the market is the real constraint, not the driving. A scooter goes where a car has to be left three streets away, and it turns a twenty-minute crawl through the old town into five. If you are staying in Nagercoil and your trips are short and frequent, this is what to hire.

By the hour, the day or the week

Short hires are normal here. People take a scooter for an afternoon of errands, for a day while a car is being serviced, or for a week while they are visiting family. The daily rate falls on a longer hire the same way a car's does, so a week is not seven times a day.

Delivered, with the paperwork done first

We bring it to you anywhere in Nagercoil — a house, a hotel, the railway junction or the Vadasery bus stand. The fuel level and the odometer reading are written down in front of you at handover and checked again when it comes back, so there is nothing to argue about at the end.

Questions about bike and scooter rental in Nagercoil

Can I rent a scooty in Nagercoil for one day?
Yes, and for less than a day. Hourly hire is normal for errands and half-day trips. Tell us roughly when you want it and when you will be done, and we will quote the whole figure before you take it.
Do I need my own helmet?
No. Helmets come with every two-wheeler, and two where the bike is being ridden two-up. Wearing them is the law in Tamil Nadu and it is also a condition of the hire.
What documents do I need to rent a bike in Nagercoil?
Your original two-wheeler driving licence and one photo ID. We take a copy at handover and give the originals straight back. A learner's licence is not enough.

How it works in Nagercoil

  • Helmets come with the vehicle — two where the bike seats two
  • A valid two-wheeler licence and one photo ID, copied at handover
  • Fuel is yours; the level at handover is what we expect back
  • Delivery anywhere in Nagercoil town, including the junction and the bus stand
Check availability in Nagercoil

or call +91 63749 42976

+ + diff --git a/blog/documents-to-rent-a-self-drive-car/index.html b/blog/documents-to-rent-a-self-drive-car/index.html new file mode 100644 index 00000000000..03d50a469e8 --- /dev/null +++ b/blog/documents-to-rent-a-self-drive-car/index.html @@ -0,0 +1,89 @@ + + + + + + + + + + + + Documents Needed to Rent a Self Drive Car in Tamil Nadu + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

What documents you need to rent a self-drive car in Tamil Nadu

A driving licence, one photo ID and a refundable deposit is the short answer. The longer answer covers international permits, what a learner's licence will not do, and what actually gets checked when the car arrives.

· checked

This is the question we are asked most often, and the short answer is dull: an original driving licence, one photo ID, and a refundable deposit. It is the exceptions that cost people a morning, so they are worth setting out properly.

The licence

It has to be an original, valid, and for the class of vehicle you are taking — a car licence for a car, a two-wheeler licence for a scooter. A photograph of it on your phone is not enough, and neither is a photocopy. We take a copy at handover and give the original straight back to you; nobody should be asking to keep your licence for the length of the hire.

A learner's licence will not do. It is not a matter of policy on our side: a learner's licence requires a qualified driver in the passenger seat, which is the opposite of what self-drive means, and the insurance on the vehicle is written on that basis.

One photo ID besides the licence

Aadhaar, a passport, a voter ID or a PAN card all work. The point of the second document is simply that it is issued by somebody other than the transport department, so the two together are harder to fake than either alone.

If you are visiting from abroad

Bring your home licence, your passport, and an international driving permit alongside the home licence — the permit is a translation, not a licence in itself, so it is only valid presented with the original. If you hold an Indian licence and live overseas, that is simpler: the Indian licence alone is fine.

Visitors home for a few weeks usually want an automatic, and there is normally one of those rather than six, so it is worth asking for it before you land rather than on the morning you want it.

The deposit

A refundable security deposit is standard across the industry and it is not a fee. It is held while you have the vehicle and returned when the vehicle comes back, less anything owing for kilometres beyond the agreed allowance or for damage. The figure depends on the vehicle, and you should be told it before you book rather than when you arrive to collect.

What actually gets checked at handover

  • The odometer reading, written down in front of you
  • The fuel level, noted the same way
  • Existing marks on the bodywork, walked round together
  • The date and time the vehicle is due back

Five minutes of this at the start is what stops a disagreement at the end. If a rental does not do it, ask them to — and if they will not, that tells you something.

The one thing people forget

Kilometre limits. Every self-drive hire includes a daily allowance, and anything past it is charged per kilometre. Both numbers should be on your booking before you leave. A day trip to Kanyakumari and back from Nagercoil is about forty kilometres; a day that takes in Padmanabhapuram and the falls at Thirparappu can be a hundred and fifty. Those are very different against a limit, and it is better to know which you are planning.

More from the blog

← All articles

+ + diff --git a/blog/driving-in-kanyakumari-district/index.html b/blog/driving-in-kanyakumari-district/index.html new file mode 100644 index 00000000000..028e0ae1d20 --- /dev/null +++ b/blog/driving-in-kanyakumari-district/index.html @@ -0,0 +1,89 @@ + + + + + + + + + + + + Driving in Kanyakumari District — Roads, Monsoon, Advice + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Driving in Kanyakumari district: roads, monsoon and what to expect

The highways are good, the town centres are slow, and the inland roads have more going on than the map suggests. What to expect, season by season.

· checked

Kanyakumari district is small and varied. You can go from a fast four-lane highway to a street laid out before cars existed in about ten minutes, and the driving on each is a different job.

The main roads

The highway between Nagercoil, Kanyakumari and on towards Thiruvananthapuram is in good condition and moves well. The coast road from Kanyakumari up towards Vattakottai and Colachel is tarred, scenic and genuinely windy — there is nothing between it and the sea, and that tells on a tall vehicle or a two-wheeler most afternoons.

The town centres

Nagercoil's centre is heavy for most of the day, and the streets around the Nagaraja temple and the market were not built for cars. If you are passing through rather than stopping, take the bypass; if you are stopping, park before ten in the morning or expect to walk. The same is true in a smaller way of Suchindram and Thuckalay around the temples.

Inland, and the hills

Going inland towards Thiruvattar, Thirparappu and the hills the surface gets patchier and the corners tighten. Nothing there needs a four-wheel drive or high clearance, but it does reward a softer-riding car and an unhurried pace. Watch for buses on the narrow sections — they will take their half out of the middle and they are not going to be the ones to give way.

The monsoon

This district gets rain from both monsoons, and the heavier one is the north-east, roughly October to December. What changes is not so much the road as the visibility and the standing water: the low stretches flood briefly and drain quickly, and the sensible response is to wait twenty minutes rather than to drive into water you cannot see the bottom of. Rain also brings the two-wheelers closer together at junctions, so leave more room than usual.

Local habits worth knowing

  • A horn here means 'I am here', not 'get out of the way' — use it on blind corners
  • Overtaking happens on both sides; check your mirrors before moving out
  • Buses and lorries assume right of way on narrow roads, and it is easier to let them have it
  • Livestock on inland roads is normal, and it does not look before crossing
  • After dark on unlit stretches, expect unlit vehicles too

If you have not driven in India before

Take an automatic, start in the morning rather than the evening, and give yourself the first day on quieter roads before taking on Nagercoil at five in the afternoon. It settles down faster than people expect — the traffic looks chaotic and is mostly predictable, and once you stop being surprised by it you are fine.

More from the blog

← All articles

+ + diff --git a/blog/index.html b/blog/index.html new file mode 100644 index 00000000000..8f2b7f1d3ae --- /dev/null +++ b/blog/index.html @@ -0,0 +1,88 @@ + + + + + + + + + + + + Blog — Driving Around Kanyakumari District + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Blog

Answers to the things people ask before they hire a vehicle — documents, distances, what a day out really takes, and what the roads are like.

+ + diff --git a/blog/km-limits-and-deposits-explained/index.html b/blog/km-limits-and-deposits-explained/index.html new file mode 100644 index 00000000000..d9f6b61d397 --- /dev/null +++ b/blog/km-limits-and-deposits-explained/index.html @@ -0,0 +1,89 @@ + + + + + + + + + + + + Car Rental KM Limits and Deposits Explained — Nagercoil + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

KM limits, extra-KM rates and deposits, explained plainly

Three numbers decide what a self-drive hire actually costs, and only one of them is the daily rate. Here is what the other two mean and how to work out whether a quote is a good one.

· checked

A daily rate on its own does not tell you what a hire costs. Three numbers do, and the other two are the ones that turn a cheap-looking quote into an expensive week.

1. The daily rate

What the vehicle costs for a day. It should come down for a longer hire — a week priced at seven times a day is not a weekly rate, it is a daily rate repeated. Ask for the weekly and monthly figures even if you are only thinking about a weekend, because a business that has them has thought about this properly.

2. The kilometre allowance

How far you can drive each day before extra charges start. This is where quotes differ most and where people get caught. A generous daily rate with a small allowance is often worse than the reverse, and which is better depends entirely on your trip.

Some real distances from Nagercoil, one way: Kanyakumari is 20 km, Suchindram 7, Thuckalay 18, Marthandam 30, Colachel 30, Thiruvananthapuram about 65. A day pottering around town might be 40 km. A day that takes in the palace, the falls and the point can be 150. Against a 150 km allowance those are very different days.

3. The extra-kilometre rate

What each kilometre past the allowance costs. It is usually a single-figure rupee amount, which sounds like nothing until you are sixty kilometres over on each of five days. Work it out against your actual plan rather than against the rate, and if you know you will go over, say so at the start — it is normally cheaper to agree a bigger allowance up front than to pay the overage.

What a deposit actually covers

A refundable security deposit is not a fee and it is not a part-payment. It is held while you have the vehicle, and it comes back when the vehicle does, minus anything genuinely owing: kilometres past the allowance, fuel if you return it lower than you took it, and damage. Nothing else should come out of it.

Questions worth asking anyone, including us

  • What is the daily allowance, and what does a kilometre past it cost?
  • What is the deposit, and when exactly does it come back?
  • Is the fuel level recorded at handover, in writing?
  • What happens if I keep the vehicle a day longer than planned?
  • Who pays if it breaks down?

A rental that answers all five without hesitating is one you can plan around. One that answers in round numbers and changes the subject is one to keep looking past.

More from the blog

← All articles

+ + diff --git a/blog/one-day-kanyakumari-by-car/index.html b/blog/one-day-kanyakumari-by-car/index.html new file mode 100644 index 00000000000..62648f6ec0b --- /dev/null +++ b/blog/one-day-kanyakumari-by-car/index.html @@ -0,0 +1,89 @@ + + + + + + + + + + + + One Day in Kanyakumari by Car — A Realistic Itinerary + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

A one-day Kanyakumari drive, with honest timings

Most one-day Kanyakumari itineraries are written by people who have not driven them. This one has the distances, the closing times and the bit where the ferry queue eats an hour.

· checked

The sights around Kanyakumari are close together, which makes a one-day plan look easier on a map than it is. The distances are short; the queues, the temple hours and the parking are what fill the day. Here is a route that fits, with the timings that make it fit.

Before sunrise — leave Nagercoil by 5:15

It is about twenty kilometres from Nagercoil to Kanyakumari, and at that hour it takes half an hour. Leaving at a quarter past five gets you parked and walking with time to spare. If you are staying in Kanyakumari itself, ten minutes is enough, which is the main argument for staying the night.

The point, and the ferry

Sunrise from the shore is free and takes as long as you want. The ferry to the Vivekananda Rock is the decision: in season the queue can be an hour each way, and that is the single thing most likely to break the rest of the day. If you want the Rock, do it first and accept that the afternoon will be shorter. If you would rather see more, skip it and come back another morning.

Vattakottai — 6 km up the coast

Fifteen minutes' drive north along the coast road, and one of the better short drives in the district with the sea on your right the whole way. The fort itself needs half an hour. It is quieter early, and the wind picks up as the morning goes on.

Suchindram — 13 km inland

The Thanumalayan temple, about twenty-five minutes from Vattakottai. The important thing here is the timing: like most temples in the district it closes in the middle of the day and opens again in the late afternoon. Aim to arrive before noon or plan to come back at four — driving there at one o'clock means looking at a closed gate.

Padmanabhapuram Palace — 35 km on

The wooden palace at Thuckalay, roughly an hour from Suchindram allowing for traffic through Nagercoil. It closes earlier than people expect and it does not admit anyone in the last part of the afternoon, so this is the stop to check the hours for on the day rather than to assume. An hour and a half inside is about right; it is bigger than it looks.

What the day adds up to

  • About 80 to 90 km of driving in total
  • Roughly three hours behind the wheel, spread across the day
  • The rest is walking, queuing and looking at things
  • Sunset back at Kanyakumari if you have the energy; most people do not

Doing it yourself, or with a driver

Self-drive suits this day if you like setting your own pace and you do not mind parking. Every stop involves leaving the car somewhere crowded and walking back to it afterwards, which is the honest argument for taking a driver instead — the driving is the easy part and the parking is not. Either works; it depends which of those bothers you more.

More from the blog

← All articles

+ + diff --git a/blog/self-drive-or-with-a-driver/index.html b/blog/self-drive-or-with-a-driver/index.html new file mode 100644 index 00000000000..1cb4d26ccc8 --- /dev/null +++ b/blog/self-drive-or-with-a-driver/index.html @@ -0,0 +1,89 @@ + + + + + + + + + + + + Self Drive or With a Driver in Kanyakumari District? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Self drive or with a driver? An honest comparison

We hire vehicles both ways, so we have no stake in the answer. Here is when each one is genuinely the better choice, including the days when self drive is a false economy.

· checked

We hire vehicles with a driver and without one, so there is no answer here that suits us better than the other. There is a real difference between the two, though, and it is not mainly about money.

Take self drive when

  • You are staying put and making short trips — errands, the market, visiting family across town
  • You want to leave when you feel like it, including before dawn and after dinner
  • You are comfortable in Indian traffic, or you are taking an automatic and the roads are quiet
  • The trip is flexible and you would rather decide as you go

Self drive is also usually cheaper for a day of ordinary running around, because you are paying for the vehicle and not for somebody's time. If your day is two short trips and a lot of sitting still, a driver spends most of it waiting, and you pay for the waiting.

Take a driver when

  • The day is a sightseeing circuit with a stop every half hour — the parking, not the driving, is the work
  • You are going a long way on the highway, particularly to Thiruvananthapuram or Madurai
  • There is an airport run at an unsociable hour
  • It is a wedding, or any day where the family should not be driving
  • Nobody in the group knows the roads and the timings

The thing people underestimate

Temple and monument hours. Most of the places worth visiting in this district shut in the middle of the day and open again late afternoon, and they are not all the same hours. A driver who does this every week knows which one to go to first; somebody with a map finds out by driving to a closed gate. Over a full day that is an hour or two, and it is the difference between seeing four things and seeing two.

The false economy

A long highway day self-driven, by somebody who is tired and does not know the road. The saving is real and it is not worth it. If the day involves three hours each way and an early start, take the driver and sleep in the back on the way home.

You can mix them

Nothing stops you hiring self drive for a week in town and taking a vehicle with a driver for the one day you are doing the circuit. That is what a lot of families visiting for a fortnight end up doing, and it is usually the cheapest version of a good trip rather than a compromise.

More from the blog

← All articles

+ + diff --git a/build.json b/build.json new file mode 100644 index 00000000000..6e1d5bb2de4 --- /dev/null +++ b/build.json @@ -0,0 +1,30 @@ +{ + "publishedAt": "2026-09-28T11:09:29.951Z", + "commit": "b1f7d84c", + "commitAt": "2026-09-28T10:56:22+00:00", + "assets": [ + "About-BagSiI9V.js", + "Bikes-BGuFlx-H.js", + "Blog-C8E-F237.js", + "CarModel-BNr5p2Tt.js", + "CarRentalAreas-BLwzjYsU.js", + "Cars-BT-gZgK7.js", + "Contact-sMllwbKq.js", + "Monthly-DUOyZQBa.js", + "NotFound-BOhuBiIT.js", + "Nri-CYBhMv_I.js", + "PageHeader-B6dZ6efH.js", + "Places-DeqB0d8-.js", + "Post-MIeUtpwm.js", + "SectionArt-CRGDPXOj.js", + "ServiceArea-ir_N5Ogd.js", + "ServicePage-2R3tb8Gb.js", + "Services-BNXO2tQW.js", + "Tariff-DM63uEOD.js", + "TouristVehicles-Or946-7L.js", + "Town-BnsId0Rm.js", + "WeddingCars-DEx5ukzM.js", + "index-BchjlLBy.js", + "index-CTc6RczQ.css" + ] +} diff --git a/car-rental/azhagiapandipuram/index.html b/car-rental/azhagiapandipuram/index.html new file mode 100644 index 00000000000..d5eb54c6acc --- /dev/null +++ b/car-rental/azhagiapandipuram/index.html @@ -0,0 +1,89 @@ + + + + + + + + + + + + Car & Bike Rental in Azhagiapandipuram — Self Drive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Self drive car & bike rental in Azhagiapandipuram

Azhagiapandipuram is a residential town rather than a stop on anyone's itinerary, and the hires here are the everyday kind: a month's car between vehicles, something for a family occasion, a bike for a few days. We deliver to the door rather than asking anyone to come to Nagercoil for it.

Getting a vehicle in Azhagiapandipuram

Azhagiapandipuram is about 15 km from our base in Nagercoil, and we deliver rather than asking you to come and collect. Give us the address and a time; the vehicle arrives with its papers, a full tank noted down and the KM reading written in front of you.

The town is a few minutes off the main Nagercoil–Thiruvananthapuram road, so getting onto the highway is quick without living on it. The lanes within the town are narrow, which is worth a thought if you are choosing between a hatchback and a seven seater.

Around Azhagiapandipuram

Known for a town in the Kalkulam country, close to Thuckalay and the palace.

  • Thuckalay, a few minutes away
  • Padmanabhapuram Palace, a short drive
  • The plantation and paddy country around the town

Drives from Azhagiapandipuram

  • Padmanabhapuram Palaceabout 5 kmClose enough for an unhurried morning
  • Nagercoilabout 15 kmBack down the main road
  • Colachelabout 18 kmSouth-west to the coast

What we hire in Azhagiapandipuram

Check availability in Azhagiapandipuram

or call +91 63749 42976

What we would bring

Maruti Suzuki Swift, Toyota Glanza Automatic, Hyundai Grand i10, Maruti Suzuki WagonR, Toyota Rumion and Toyota Innova. Rates for all of them are on the tariff page, and they come down on a longer hire.

+ + diff --git a/car-rental/boothapandi/index.html b/car-rental/boothapandi/index.html new file mode 100644 index 00000000000..c42ee1b7c53 --- /dev/null +++ b/car-rental/boothapandi/index.html @@ -0,0 +1,89 @@ + + + + + + + + + + + + Self Drive Car Rental in Boothapandi, Kanyakumari + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Self drive car & bike rental in Boothapandi

Boothapandi is a quick delivery from Nagercoil and sits on the road heading north-east, which is the way out of the district towards Tirunelveli and Madurai. A fair share of the hires here are for exactly that — a run north and back over two or three days.

Getting a vehicle in Boothapandi

Boothapandi is about 11 km from our base in Nagercoil, and we deliver rather than asking you to come and collect. Give us the address and a time; the vehicle arrives with its papers, a full tank noted down and the KM reading written in front of you.

The road north climbs gradually and the wind through the gap is strong enough to be felt in a small car, particularly in the afternoon. It is a well surfaced road and an easy drive; the wind is simply worth knowing about before you meet it.

Around Boothapandi

Known for the town on the Pazhayar, on the road north towards Tirunelveli.

  • The Pazhayar river crossing
  • The road north towards Tirunelveli and the wind farms
  • The paddy country south of the town

Drives from Boothapandi

  • Nagercoilabout 11 kmFifteen minutes back into town
  • Tirunelveliabout 70 kmNorth through the gap, the main road out of the district
  • Kanyakumariabout 31 kmSouth, past Nagercoil

What we hire in Boothapandi

Check availability in Boothapandi

or call +91 63749 42976

What we would bring

Maruti Suzuki Swift, Toyota Glanza Automatic, Hyundai Grand i10, Maruti Suzuki WagonR, Toyota Rumion and Toyota Innova. Rates for all of them are on the tariff page, and they come down on a longer hire.

+ + diff --git a/car-rental/colachel/index.html b/car-rental/colachel/index.html new file mode 100644 index 00000000000..b2e934a1be7 --- /dev/null +++ b/car-rental/colachel/index.html @@ -0,0 +1,89 @@ + + + + + + + + + + + + Car & Bike Rental in Colachel — Self Drive Hire + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Self drive car & bike rental in Colachel

Colachel is a working harbour town rather than a tourist one, and the hires here are mostly practical: getting a family to a wedding inland, a month's use while a car is off the road, or a day out along the coast. The beach north of the harbour is quiet on a weekday in a way that Kanyakumari never is.

Getting a vehicle in Colachel

Colachel is about 20 km from our base in Nagercoil, and we deliver rather than asking you to come and collect. Give us the address and a time; the vehicle arrives with its papers, a full tank noted down and the KM reading written in front of you.

The road along the coast is narrow in places and shared with fishing traffic early in the morning, so nobody in a hurry should take it at six. The inland road to Nagercoil is the faster of the two and the one we normally suggest.

Around Colachel

Known for a fishing harbour on the west coast, and the battle fought here in 1741.

  • The fishing harbour and the boats coming in
  • The 1741 battle memorial, from the Travancore victory over the Dutch
  • The beach north of the harbour, quiet on weekdays

Drives from Colachel

  • Nagercoilabout 20 kmThe inland road, quicker than the coast
  • Padmanabhapuram Palaceabout 14 kmNorth, through the plantation country
  • Kanyakumariabout 40 kmThe long way round the coast, worth it once

What we hire in Colachel

Check availability in Colachel

or call +91 63749 42976

What we would bring

Maruti Suzuki Swift, Toyota Glanza Automatic, Hyundai Grand i10, Maruti Suzuki WagonR, Toyota Rumion and Toyota Innova. Rates for all of them are on the tariff page, and they come down on a longer hire.

+ + diff --git a/car-rental/eraniel/index.html b/car-rental/eraniel/index.html new file mode 100644 index 00000000000..c658f6c7958 --- /dev/null +++ b/car-rental/eraniel/index.html @@ -0,0 +1,89 @@ + + + + + + + + + + + + Car & Bike Rental in Eraniel — Self Drive Hire + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Self drive car & bike rental in Eraniel

Eraniel is a short delivery from our base and sits between the district town and the western coast, which makes it a practical place to start a drive in either direction. Most of the hires here are family ones — a wedding, a temple visit, a week while a car is in the workshop.

Getting a vehicle in Eraniel

Eraniel is about 20 km from our base in Nagercoil, and we deliver rather than asking you to come and collect. Give us the address and a time; the vehicle arrives with its papers, a full tank noted down and the KM reading written in front of you.

The roads around Eraniel run between paddy fields and are narrow, level and easy driving, which makes them a gentle place to get used to an unfamiliar car before joining the main road.

Around Eraniel

Known for a small town in the paddy country between Nagercoil and the coast.

  • The paddy country around the town
  • The road west towards Colachel and the sea
  • Padmanabhapuram, a short drive north

Drives from Eraniel

  • Padmanabhapuram Palaceabout 7 kmNorth, easily done in a morning
  • Colachelabout 7 kmWest to the harbour and the beach
  • Nagercoilabout 20 kmEast into town

What we hire in Eraniel

Check availability in Eraniel

or call +91 63749 42976

What we would bring

Maruti Suzuki Swift, Toyota Glanza Automatic, Hyundai Grand i10, Maruti Suzuki WagonR, Toyota Rumion and Toyota Innova. Rates for all of them are on the tariff page, and they come down on a longer hire.

+ + diff --git a/car-rental/index.html b/car-rental/index.html new file mode 100644 index 00000000000..65bc549a04b --- /dev/null +++ b/car-rental/index.html @@ -0,0 +1,88 @@ + + + + + + + + + + + + Car & Bike Rental Across Kanyakumari District + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Where we deliver across Kanyakumari district

We bring the vehicle to you rather than asking you to come and fetch it. Twelve towns with a page of their own below, and everywhere in between them on request — tell us the address and we will say yes or tell you what it costs.

Distances are approximate road distances from Nagercoil, where the vehicles are kept.

Somewhere not on this list? Ask us — most of Kanyakumari district is a short enough run that the answer is yes.

+ + diff --git a/car-rental/kanyakumari/index.html b/car-rental/kanyakumari/index.html new file mode 100644 index 00000000000..ce14085f8e5 --- /dev/null +++ b/car-rental/kanyakumari/index.html @@ -0,0 +1,89 @@ + + + + + + + + + + + + Car & Bike Rental in Kanyakumari — Self Drive Hire + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Self drive car & bike rental in Kanyakumari

Almost everyone who hires a vehicle in Kanyakumari is staying a night or two and wants to see more than the tip itself. A car for a day covers the temples inland and the coast road west, which no tour bus timetable will give you, and a two wheeler is the easier thing for getting around the town itself.

Getting a vehicle in Kanyakumari

Kanyakumari is about 20 km from our base in Nagercoil, and we deliver rather than asking you to come and collect. Give us the address and a time; the vehicle arrives with its papers, a full tank noted down and the KM reading written in front of you.

The streets around the ferry and the beach are narrow and busy from before dawn, because the sunrise crowd starts early. Parking is far easier a few streets back from the seafront, and we can deliver to any of the hotels along the main road rather than meeting in the middle of that.

Around Kanyakumari

Known for the southern tip of the mainland, and the reason most visitors come to the district.

  • The Vivekananda Rock Memorial, reached by ferry
  • The Thiruvalluvar Statue on the neighbouring rock
  • Bhagavathy Amman Temple, by the shore
  • The sunrise and sunset points, at opposite ends of the same beach

Drives from Kanyakumari

  • Suchindramabout 13 kmThe Thanumalayan temple, on the road back towards Nagercoil
  • Padmanabhapuram Palaceabout 40 kmThe wooden palace of the old Travancore capital
  • Colachelabout 40 kmVia Nagercoil, or the slower coast road

What we hire in Kanyakumari

Check availability in Kanyakumari

or call +91 63749 42976

What we would bring

Maruti Suzuki Swift, Toyota Glanza Automatic, Hyundai Grand i10, Maruti Suzuki WagonR, Toyota Rumion and Toyota Innova. Rates for all of them are on the tariff page, and they come down on a longer hire.

+ + diff --git a/car-rental/kuzhithurai/index.html b/car-rental/kuzhithurai/index.html new file mode 100644 index 00000000000..64a59ff8f82 --- /dev/null +++ b/car-rental/kuzhithurai/index.html @@ -0,0 +1,89 @@ + + + + + + + + + + + + Car & Bike Rental in Kuzhithurai — Self Drive Hire + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Self drive car & bike rental in Kuzhithurai

Kuzhithurai is the far end of the district from us and the end that looks north, so the hires here run that way: airport runs, monthly cars for people working across the border, and vehicles for families who live here and travel to Kerala rather than to Nagercoil. We deliver this far out; it is worth telling us the day before rather than the morning of.

Getting a vehicle in Kuzhithurai

Kuzhithurai is about 26 km from our base in Nagercoil, and we deliver rather than asking you to come and collect. Give us the address and a time; the vehicle arrives with its papers, a full tank noted down and the KM reading written in front of you.

The highway through Kuzhithurai is the quick route into Kerala, and the crossing is straightforward. Anyone planning to drive across should tell us, because the paperwork we hand over matters more once you are out of Tamil Nadu.

Around Kuzhithurai

Known for the last town of any size before the Kerala border.

  • The NH-66 crossing towards Kerala
  • The rubber and plantation country on both sides of the town
  • The weekly market

Drives from Kuzhithurai

  • Thiruvananthapuram airportabout 42 kmAbout an hour in ordinary traffic
  • Marthandamabout 5 kmThe next town down the highway
  • Thiruvattarabout 15 kmEast, towards the temple and the river

What we hire in Kuzhithurai

Check availability in Kuzhithurai

or call +91 63749 42976

What we would bring

Maruti Suzuki Swift, Toyota Glanza Automatic, Hyundai Grand i10, Maruti Suzuki WagonR, Toyota Rumion and Toyota Innova. Rates for all of them are on the tariff page, and they come down on a longer hire.

+ + diff --git a/car-rental/marthandam/index.html b/car-rental/marthandam/index.html new file mode 100644 index 00000000000..16b1910eac1 --- /dev/null +++ b/car-rental/marthandam/index.html @@ -0,0 +1,89 @@ + + + + + + + + + + + + Self Drive Car Rental in Marthandam, Kanyakumari + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Self drive car & bike rental in Marthandam

Marthandam sits on the main road north-west, which makes it the part of the district where a car is most often hired for a month rather than a day. Families working in Thiruvananthapuram, people back from the Gulf for a few weeks, and anyone who has to make the Trivandrum airport run more than once — that is most of what we deliver here.

Getting a vehicle in Marthandam

Marthandam is about 26 km from our base in Nagercoil, and we deliver rather than asking you to come and collect. Give us the address and a time; the vehicle arrives with its papers, a full tank noted down and the KM reading written in front of you.

Being on the highway cuts both ways: getting out of Marthandam towards Kerala is quick, and getting across the town at market time is not. The rubber estates on either side of the road mean the side roads are narrow and shaded, and they are worth taking slowly after rain.

Around Marthandam

Known for the trading town of the rubber belt, on the highway towards Thiruvananthapuram.

  • The market, busiest early in the week
  • The rubber estates that surround the town
  • The NH-66 junction, the fast way to Thiruvananthapuram

Drives from Marthandam

  • Thiruvananthapuram airportabout 45 kmThe run most of our monthly hires here are for
  • Padmanabhapuram Palaceabout 8 kmClose enough to do in a morning
  • Kuzhithuraiabout 5 kmFurther up the same highway

What we hire in Marthandam

Check availability in Marthandam

or call +91 63749 42976

What we would bring

Maruti Suzuki Swift, Toyota Glanza Automatic, Hyundai Grand i10, Maruti Suzuki WagonR, Toyota Rumion and Toyota Innova. Rates for all of them are on the tariff page, and they come down on a longer hire.

+ + diff --git a/car-rental/nagercoil/index.html b/car-rental/nagercoil/index.html new file mode 100644 index 00000000000..bfa573ff8a7 --- /dev/null +++ b/car-rental/nagercoil/index.html @@ -0,0 +1,89 @@ + + + + + + + + + + + + Self Drive Car & Bike Rental in Nagercoil + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Self drive car & bike rental in Nagercoil

Nagercoil is where we are based, so this is the quickest place in the district to get a vehicle. If you are at the railway junction, the bus stand, a hotel in town or your own front door, we can usually have something with you the same day, and often within the hour.

Getting a vehicle in Nagercoil

Nagercoil is where the vehicles are kept, so there is no delivery to wait for. Tell us when you want it and it is ready when you arrive — or we bring it to you anywhere in town.

The town is built around the Nagaraja temple, and traffic through the centre is heavy for most of the day. Anyone driving here for the first time is better off taking the bypass rather than the old town roads, and parking near the market is easier before ten in the morning.

Around Nagercoil

Known for the district headquarters, and where our vehicles are kept.

  • Nagaraja Temple, in the centre of town
  • Nagercoil Junction, on the Thiruvananthapuram line
  • The Vadasery bus stand, for connections across the district

Drives from Nagercoil

  • Kanyakumariabout 20 kmFor sunrise, the Vivekananda Rock and the Thiruvalluvar statue
  • Suchindramabout 7 kmThe Thanumalayan temple, a short run out of town
  • Maruthuvazhmalaiabout 11 kmA hill walk, best early in the day

What we hire in Nagercoil

Check availability in Nagercoil

or call +91 63749 42976

What we would bring

Maruti Suzuki Swift, Toyota Glanza Automatic, Hyundai Grand i10, Maruti Suzuki WagonR, Toyota Rumion and Toyota Innova. Rates for all of them are on the tariff page, and they come down on a longer hire.

+ + diff --git a/car-rental/padmanabhapuram/index.html b/car-rental/padmanabhapuram/index.html new file mode 100644 index 00000000000..f9bfe4911b3 --- /dev/null +++ b/car-rental/padmanabhapuram/index.html @@ -0,0 +1,89 @@ + + + + + + + + + + + + Car Rental in Padmanabhapuram — Self Drive Hire + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Self drive car & bike rental in Padmanabhapuram

Most people come to Padmanabhapuram for the palace, and most of them see it as one stop on somebody else's timetable. A car for the day is the difference between forty minutes in the palace and a morning in it, with the rest of the day for the temples and the coast rather than a coach back.

Getting a vehicle in Padmanabhapuram

Padmanabhapuram is about 20 km from our base in Nagercoil, and we deliver rather than asking you to come and collect. Give us the address and a time; the vehicle arrives with its papers, a full tank noted down and the KM reading written in front of you.

The palace closes for a long lunch and on Mondays, which catches out a lot of visitors — worth checking before setting off. Parking near the entrance fills by mid-morning in season, and there is more of it a short walk back towards Thuckalay.

Around Padmanabhapuram

Known for the wooden palace of the old Travancore capital.

  • Padmanabhapuram Palace, the largest wooden palace complex in Asia
  • The old fort walls around the palace
  • Thuckalay town, a few minutes away

Drives from Padmanabhapuram

  • Thiruvattarabout 14 kmThe Adikesava Perumal temple, on the river
  • Colachelabout 14 kmSouth to the coast and the harbour
  • Nagercoilabout 20 kmBack towards the district town

What we hire in Padmanabhapuram

Check availability in Padmanabhapuram

or call +91 63749 42976

What we would bring

Maruti Suzuki Swift, Toyota Glanza Automatic, Hyundai Grand i10, Maruti Suzuki WagonR, Toyota Rumion and Toyota Innova. Rates for all of them are on the tariff page, and they come down on a longer hire.

+ + diff --git a/car-rental/suchindram/index.html b/car-rental/suchindram/index.html new file mode 100644 index 00000000000..9ca8c5a8db0 --- /dev/null +++ b/car-rental/suchindram/index.html @@ -0,0 +1,89 @@ + + + + + + + + + + + + Car & Bike Rental in Suchindram, Kanyakumari + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Self drive car & bike rental in Suchindram

Suchindram is close enough to Nagercoil that delivery here is quick and the same-day answer is almost always yes. It sits on the road between Nagercoil and Kanyakumari, which makes it a natural first or last stop on a day out rather than a trip of its own.

Getting a vehicle in Suchindram

Suchindram is about 7 km from our base in Nagercoil, and we deliver rather than asking you to come and collect. Give us the address and a time; the vehicle arrives with its papers, a full tank noted down and the KM reading written in front of you.

The temple street is narrow and the crowd builds around the main pooja times, so a car is better left at the edge of it. A two wheeler is genuinely easier here, and a good number of our hires in Suchindram are exactly that.

Around Suchindram

Known for the Thanumalayan temple, and its musical pillars.

  • Thanumalayan Temple, with its carved musical pillars
  • The Hanuman statue inside the temple, one of the tallest of its kind
  • The temple tank

Drives from Suchindram

  • Kanyakumariabout 13 kmStraight on down the same road
  • Nagercoilabout 7 kmTen minutes back into town
  • Maruthuvazhmalaiabout 5 kmThe hill, for a morning walk

What we hire in Suchindram

Check availability in Suchindram

or call +91 63749 42976

What we would bring

Maruti Suzuki Swift, Toyota Glanza Automatic, Hyundai Grand i10, Maruti Suzuki WagonR, Toyota Rumion and Toyota Innova. Rates for all of them are on the tariff page, and they come down on a longer hire.

+ + diff --git a/car-rental/thiruvattar/index.html b/car-rental/thiruvattar/index.html new file mode 100644 index 00000000000..3675acc9908 --- /dev/null +++ b/car-rental/thiruvattar/index.html @@ -0,0 +1,89 @@ + + + + + + + + + + + + Self Drive Car Rental in Thiruvattar, Kanyakumari + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Self drive car & bike rental in Thiruvattar

Thiruvattar is inland and north, in the quieter part of the district, and public transport out of it is thin enough that a vehicle changes what a day can hold. People hire here to reach the temple without a bus timetable, and to get across to Padmanabhapuram and the coast in the same trip.

Getting a vehicle in Thiruvattar

Thiruvattar is about 30 km from our base in Nagercoil, and we deliver rather than asking you to come and collect. Give us the address and a time; the vehicle arrives with its papers, a full tank noted down and the KM reading written in front of you.

The roads north of the town run through plantation and paddy country and are pleasant driving, but they are narrow and unlit after dark. Anyone heading back to Nagercoil in the evening is better off leaving before the light goes.

Around Thiruvattar

Known for the Adikesava Perumal temple, on an island between two rivers.

  • Adikesava Perumal Temple, between the Kothai and Paraliyar rivers
  • The river and the paddy fields around the town
  • The plantation roads running north towards the hills

Drives from Thiruvattar

  • Padmanabhapuram Palaceabout 14 kmSouth, an easy half day
  • Kuzhithuraiabout 15 kmWest, towards the highway
  • Nagercoilabout 30 kmBack to the district town

What we hire in Thiruvattar

Check availability in Thiruvattar

or call +91 63749 42976

What we would bring

Maruti Suzuki Swift, Toyota Glanza Automatic, Hyundai Grand i10, Maruti Suzuki WagonR, Toyota Rumion and Toyota Innova. Rates for all of them are on the tariff page, and they come down on a longer hire.

+ + diff --git a/car-rental/thuckalay/index.html b/car-rental/thuckalay/index.html new file mode 100644 index 00000000000..1858f7f52e4 --- /dev/null +++ b/car-rental/thuckalay/index.html @@ -0,0 +1,89 @@ + + + + + + + + + + + + Self Drive Car Rental in Thuckalay, Kanyakumari + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Self drive car & bike rental in Thuckalay

Thuckalay is close enough to Nagercoil for a same-day delivery and far enough along the Kerala road to be a sensible place to start a drive north. It is also the nearest town to Padmanabhapuram Palace, so a fair number of the day hires here are people who want the palace without the coach party.

Getting a vehicle in Thuckalay

Thuckalay is about 16 km from our base in Nagercoil, and we deliver rather than asking you to come and collect. Give us the address and a time; the vehicle arrives with its papers, a full tank noted down and the KM reading written in front of you.

The main road through Thuckalay carries the traffic between Nagercoil and Thiruvananthapuram, which makes it slow through the middle of the day and clear early. The roads towards the palace and the villages behind it are quieter and well surfaced.

Around Thuckalay

Known for the town beside Padmanabhapuram, on the road between Nagercoil and Kerala.

  • Padmanabhapuram Palace, a short drive away
  • The Nagercoil–Thiruvananthapuram highway through the town
  • The paddy country on the roads behind the town

Drives from Thuckalay

  • Padmanabhapuram Palaceabout 3 kmNear enough to walk, easier to park a car nearby
  • Thiruvattarabout 14 kmThe Adikesava Perumal temple, north on a quiet road
  • Nagercoilabout 16 kmTwenty minutes when the road is clear

What we hire in Thuckalay

Check availability in Thuckalay

or call +91 63749 42976

What we would bring

Maruti Suzuki Swift, Toyota Glanza Automatic, Hyundai Grand i10, Maruti Suzuki WagonR, Toyota Rumion and Toyota Innova. Rates for all of them are on the tariff page, and they come down on a longer hire.

+ + diff --git a/cars/glanza-automatic/index.html b/cars/glanza-automatic/index.html new file mode 100644 index 00000000000..4dcf20f37ba --- /dev/null +++ b/cars/glanza-automatic/index.html @@ -0,0 +1,90 @@ + + + + + + + + + + + + Glanza Automatic Self Drive Car Hire in Nagercoil + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Toyota Glanza Automatic on self drive hire

The Glanza is our automatic. No clutch, no gear lever to think about — which sounds like a small thing until you are in the queue on the Nagercoil bypass at six in the evening, or you have spent the last ten years driving an automatic somewhere else.

Who the Glanza Automatic suits

Anyone who has not driven a manual in years, visitors home from the Gulf, Singapore or Malaysia, and anybody who would rather spend a day of Indian traffic thinking about the road instead of the clutch.

What it is like to drive here

In town it does the thing automatics do: the car creeps forward on its own and you steer. On the open road to Kanyakumari or up towards Thirparappu it holds a gear sensibly and does not hunt. The cabin is a little wider than the Swift's, so three in the back is less of a negotiation. It is the same footprint to park.

  • No clutch — the single biggest difference in town traffic
  • The roomiest cabin of the hatchbacks we keep
  • Quiet at highway speed

Worth knowing

An automatic uses a little more fuel than the same car as a manual, and fuel is yours on a self-drive hire. Over a week that is real money, so if you are happy with a clutch the Swift or the Grand i10 will cost less to run.

Questions about the Glanza Automatic

I have an overseas licence. Can I hire the automatic?
Yes. Bring your passport, your home licence and an international driving permit alongside it. We take copies at handover, the same as for anyone else. The automatic is usually the one visitors ask for, so it is worth booking it before you land.
Is an automatic harder to drive on the ghat roads?
No — easier, if anything. On the climbs inland you are not changing gear on a slope, and coming down you can hold a lower gear. What it will not do is engine-brake as hard as a manual in low gear, so give yourself more room on a long descent.
Is the Glanza the same as a Baleno?
It is the same car underneath, sold by Toyota rather than Maruti, with Toyota's badge and service. For a hire it makes no practical difference to you except the name on the front.

The Glanza Automatic, in figures

Seats
5
Fuel
Petrol
Gearbox
Automatic
Type
Hatchback
Luggage
Two medium suitcases and a soft bag
Per day
On request
KM included each day
Agreed when you book
Extra KM
—
Refundable deposit
—
Check Glanza Automatic availability

or call +91 63749 42976

Every rate on this page is the whole rate. See the full tariff for how the daily figure falls on a weekly or monthly hire.

+ + diff --git a/cars/grand-i10/index.html b/cars/grand-i10/index.html new file mode 100644 index 00000000000..d72e90f6741 --- /dev/null +++ b/cars/grand-i10/index.html @@ -0,0 +1,90 @@ + + + + + + + + + + + + Hyundai Grand i10 Self Drive Rental, Kanyakumari District + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Hyundai Grand i10 on self drive hire

If the Swift is the sharp one, the Grand i10 is the comfortable one. Softer suspension, a light clutch and a cabin that feels a size bigger than the car looks — it is the hatchback we hand to somebody who has not driven for a while.

Who the Grand i10 suits

First-time renters, families of four, and anyone whose route includes the inland roads where the surface is patchy.

What it is like to drive here

The suspension is set up for rough surfaces rather than for cornering, which is the right trade for most of this district: the road out towards Thiruvattar and Thirparappu is exactly where you notice it. Controls are light all round. It is a touch softer through fast corners than a Swift, so it rewards an unhurried pace, which is the pace these roads want anyway.

  • The most comfortable ride of the hatchbacks over broken surfaces
  • Light controls — an easy car to get used to quickly
  • Good visibility out of the front and sides

Worth knowing

The soft setup means more body roll on a fast road. It is not a car to hurry on the highway, and the boot is the same two-case story as the Swift.

Questions about the Grand i10

Is the Grand i10 good for a first-time self-drive hire?
It is the one we usually suggest. The clutch is light, the visibility is good and nothing about it is sharp or sudden. If you have not driven in India before, this or the Glanza Automatic is where to start.
Can it handle the road to Thirparappu falls?
Yes. It is a tarred road the whole way with some broken stretches, and the Grand i10's softer suspension is better suited to it than a firmer car. Take it steadily over the rough sections and there is nothing there a hatchback cannot do.
How many bags fit?
Two medium suitcases in the boot, or one large case and soft bags on the parcel shelf. Four people with a week's luggage is the point at which to move up to a seven-seater.

The Grand i10, in figures

Seats
5
Fuel
Petrol
Gearbox
Manual
Type
Hatchback
Luggage
Two medium suitcases
Per day
On request
KM included each day
Agreed when you book
Extra KM
—
Refundable deposit
—
Check Grand i10 availability

or call +91 63749 42976

Every rate on this page is the whole rate. See the full tariff for how the daily figure falls on a weekly or monthly hire.

+ + diff --git a/cars/index.html b/cars/index.html new file mode 100644 index 00000000000..0667a2a09dc --- /dev/null +++ b/cars/index.html @@ -0,0 +1,88 @@ + + + + + + + + + + + + Self Drive Car Hire in Nagercoil & Kanyakumari + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Our cars

Hatchbacks, sedans, SUVs and 7 seater vehicles for self-drive hire. Every car lists its KM limit, extra-KM rate and deposit, so there is nothing to discover later.

Which one to take

Most of the choice comes down to three questions: how many of you there are, how far you are going, and whether you want to drive a manual. Every vehicle below has a page of its own saying what it is like on these roads — including what it is not good at.

Two or three of you, staying around town
A hatchback. It is the cheapest to hire and the cheapest to fuel, it parks where the bigger vehicles cannot, and for the distances inside this district there is nothing it will not do comfortably.
You would rather not use a clutch
The automatic. It is the one visitors home from abroad ask for and the one to take if Nagercoil traffic at six in the evening is not something you want to think about. There is normally one rather than six, so ask early.
Six or seven, or real luggage
A seven-seater. Petrol if you are staying in the district and watching the fuel bill, diesel if the day involves a long highway run or a full load. Five adults with a week of luggage is the point at which a hatchback stops being the right answer.

Still deciding? Self drive or with a driver covers the days when paying for somebody else to drive is the cheaper answer, and the tariff has every figure for every vehicle.

What every hire includes

  • A daily kilometre allowance, stated before you book, with the extra-KM rate beside it
  • A refundable deposit — the figure is told to you in advance, not at handover
  • The odometer and fuel level written down in front of you at collection and return
  • Delivery and collection anywhere in Kanyakumari district
  • Insurance and papers in the vehicle, and servicing that is ours to worry about

What you need to bring is a licence, one photo ID and the deposit — the documents in full.

Our fleet

Rates shown are per day. Longer hires bring the daily rate down.

+ + diff --git a/cars/innova/index.html b/cars/innova/index.html new file mode 100644 index 00000000000..9d2af7db4bd --- /dev/null +++ b/cars/innova/index.html @@ -0,0 +1,90 @@ + + + + + + + + + + + + Toyota Innova Self Drive & Tour Hire, Nagercoil + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Toyota Innova on self drive hire

The Innova is the long-distance vehicle. It is what gets booked for a wedding party, for a Thiruvananthapuram airport run at four in the morning, and for the multi-day tours where seven people and their luggage have to travel together and arrive unbothered.

Who the Innova suits

Wedding parties, airport transfers, multi-day tours, and any group of six or seven with real luggage.

What it is like to drive here

It is the most settled vehicle we keep on a highway — heavy, quiet at speed and comfortable over long distances in a way the hatchbacks are not. The diesel pulls from low revs, which suits a loaded vehicle on the climbs inland. In town it is the biggest thing we hand over, so allow for the length when parking, and think ahead on the narrow temple streets.

  • The best long-distance comfort in the fleet, loaded or empty
  • A third row adults can sit in for hours
  • Boot space for a full group's luggage

Worth knowing

It is long, and the streets around the older temples were not built for it. For town errands and tight parking a hatchback is the easier choice; take the Innova for the distance, not for the market run.

Questions about the Innova

Can I hire an Innova with a driver?
Yes. Self drive and with a driver are both possible — for airport runs at odd hours and for multi-day tours most people take a driver. Tell us which you want when you enquire and the rate is quoted accordingly.
Is the Innova used for weddings?
It is the usual choice for the family vehicles alongside a decorated car for the couple. Muhurtham dates go months ahead, so book early — see our wedding cars page for how that works.
Is it worth it for a trip inside the district?
Only if you need the seats or the luggage space. For four people staying inside Kanyakumari district a hatchback costs less to hire and less to fuel. The Innova earns its rate on distance, on load, and on comfort over a long day.

The Innova, in figures

Seats
7
Fuel
Diesel
Gearbox
Manual
Type
MUV
Luggage
Large — suitcases for a full group, with all three rows in use
Per day
On request
KM included each day
Agreed when you book
Extra KM
—
Refundable deposit
—
Check Innova availability

or call +91 63749 42976

Every rate on this page is the whole rate. See the full tariff for how the daily figure falls on a weekly or monthly hire.

+ + diff --git a/cars/rumion/index.html b/cars/rumion/index.html new file mode 100644 index 00000000000..7a9686e2dc0 --- /dev/null +++ b/cars/rumion/index.html @@ -0,0 +1,90 @@ + + + + + + + + + + + + Toyota Rumion 7 Seater Self Drive Hire in Kanyakumari + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Toyota Rumion on self drive hire

The Rumion is a seven-seater that runs on petrol, and that combination is why people take it. For a family group staying inside the district it costs noticeably less to run over a day than a diesel seven-seater, without giving up the seats.

Who the Rumion suits

Family groups of five to seven, temple circuits, and weekend trips where everybody wants to travel in one vehicle rather than two.

What it is like to drive here

It drives much like a large hatchback rather than like a van — the controls are light and it is not intimidating to place on a narrow road. The middle row is the comfortable one; the third row is genuinely usable for children and for adults over shorter distances. On the coast road to Colachel and the run out to Kanyakumari it is an easy, unhurried vehicle.

  • Seven seats without diesel running costs
  • Light to drive for its size
  • The middle row slides, so you can trade legroom between rows two and three

Worth knowing

With all seven seats up the boot is small — enough for a day out, not for seven people's luggage. For a wedding party or a multi-day tour with bags, the Innova is the one.

Questions about the Rumion

Can seven adults actually sit in a Rumion?
Seven adults fit for a trip across the district. For a long day, the honest arrangement is five adults and two children, or six adults with the middle row slid back. The third row is a real seat, not a token one, but it is the smallest.
Rumion or Innova for a family trip?
Rumion if you are staying in the district and watching the fuel bill — it is petrol and lighter. Innova if you are going further, carrying luggage for everybody, or want the most comfortable third row. We will tell you which is free on your dates either way.
Is there luggage space with all seven seats in use?
Enough for a day out — bags, water, a couple of small cases. Not enough for seven people's suitcases. Fold the third row and the boot becomes large, which is how most people use it: six travelling, or seven with light bags.

The Rumion, in figures

Seats
7
Fuel
Petrol
Gearbox
Manual
Type
MUV
Luggage
Modest with all seven seats up; large with the third row folded
Per day
On request
KM included each day
Agreed when you book
Extra KM
—
Refundable deposit
—
Check Rumion availability

or call +91 63749 42976

Every rate on this page is the whole rate. See the full tariff for how the daily figure falls on a weekly or monthly hire.

+ + diff --git a/cars/swift/index.html b/cars/swift/index.html new file mode 100644 index 00000000000..e086207bfc1 --- /dev/null +++ b/cars/swift/index.html @@ -0,0 +1,90 @@ + + + + + + + + + + + + Swift Self Drive Car Rental in Nagercoil & Kanyakumari + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Maruti Suzuki Swift on self drive hire

The Swift is the car most people picture when they think of a self-drive hatchback, and it is the one we hand over most often. It is short, light to steer and returns the kind of mileage that matters when a day out means two hundred kilometres of coast road.

Who the Swift suits

Couples, a family of three or four, and anyone who is going to be parking in Nagercoil town rather than only on the highway.

What it is like to drive here

The steering is light and the turning circle is small, which is what you want in the streets around the Nagaraja temple and the market, where a wider car has to be reversed out of things. The flip side is a firm ride: over the broken stretches inland towards Thiruvattar you feel more of the road than you would in a Grand i10. On the Kanyakumari highway it sits comfortably at highway speeds and is quiet enough to talk in.

  • Easy to park anywhere in town, including the older streets
  • Light clutch and gearshift, which tells in stop-start traffic
  • The best fuel economy of the hatchbacks we keep

Worth knowing

The ride is on the firm side, and the boot takes two medium cases rather than three. For five adults with luggage for a week, take the Rumion or the Innova instead.

Questions about the Swift

Is the Swift available as an automatic?
The Swift we keep is a manual. If you would rather not use a clutch, take the Glanza Automatic — it is the automatic in our fleet and it is the same size of car to park.
Will a Swift take five adults to Kanyakumari and back?
For the twenty kilometres to Kanyakumari, yes, comfortably enough. For a full day with five adults and bags, a seven-seater is the honest answer — the Swift's back seat is a three-person bench, and the boot is the limit before the seat is.
What mileage does it give on a Kanyakumari trip?
On a mixed run of town and highway you can expect somewhere around 18 to 20 kilometres to the litre with a light foot. Fuel is yours on a self-drive hire, so this is a real difference between a Swift and a seven-seater over a long day.

The Swift, in figures

Seats
5
Fuel
Petrol
Gearbox
Manual
Type
Hatchback
Luggage
Two medium suitcases, or one large and a couple of bags
Per day
On request
KM included each day
Agreed when you book
Extra KM
—
Refundable deposit
—
Check Swift availability

or call +91 63749 42976

Every rate on this page is the whole rate. See the full tariff for how the daily figure falls on a weekly or monthly hire.

+ + diff --git a/cars/wagonr/index.html b/cars/wagonr/index.html new file mode 100644 index 00000000000..9bcc5143a72 --- /dev/null +++ b/cars/wagonr/index.html @@ -0,0 +1,90 @@ + + + + + + + + + + + + WagonR Self Drive Car Rental in Nagercoil, Kanyakumari + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Maruti Suzuki WagonR on self drive hire

The WagonR is the tall one, and that is the whole point of it. High roof, upright seats and big doors — you step in rather than drop down into it, and you step out the same way.

Who the WagonR suits

Anyone travelling with older parents or grandparents, anyone with a knee or a back that objects to a low seat, and temple visits where the parking is tight and the streets are narrow.

What it is like to drive here

You sit high and see over the traffic, which makes town driving less of a guess. It is narrow for its height, so it goes down streets a wider car has to back out of — worth knowing around Suchindram and the older parts of Nagercoil. In return it feels the wind on the open coastal stretch towards Colachel, so it is happier at an easy pace than a hurried one.

  • The easiest car in the fleet to get in and out of
  • You sit high, so you can see over traffic
  • Narrow enough for the old temple streets, with a tall boot

Worth knowing

The tall body catches side wind on the open coast road, and it is the least settled of our hatchbacks at highway speed. For a long run to Thiruvananthapuram, take the Innova.

Questions about the WagonR

Is the WagonR easier for elderly passengers?
That is the main reason to choose it. The seat is at about the height of a dining chair rather than down near the floor, and the doors open wide, so getting in and out does not involve lowering yourself into the car.
Will it fit down the streets around the temples?
Better than most. It is genuinely narrow for how roomy it feels inside, which is exactly what you want around Suchindram or the older parts of Nagercoil where the street was laid out long before cars.
Is it comfortable on the highway?
For district distances, yes. For a long highway run — Thiruvananthapuram airport and back, say — a lower, heavier car like the Innova is the more settled choice, particularly when it is windy on the coast.

The WagonR, in figures

Seats
5
Fuel
Petrol
Gearbox
Manual
Type
Hatchback
Luggage
Two medium suitcases, and a tall boot for awkward shapes
Per day
On request
KM included each day
Agreed when you book
Extra KM
—
Refundable deposit
—
Check WagonR availability

or call +91 63749 42976

Every rate on this page is the whole rate. See the full tariff for how the daily figure falls on a weekly or monthly hire.

+ + diff --git a/contact/index.html b/contact/index.html new file mode 100644 index 00000000000..fa2d3770317 --- /dev/null +++ b/contact/index.html @@ -0,0 +1,88 @@ + + + + + + + + + + + + Contact — Car & Bike Rental in Nagercoil + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Contact

Call us, write to us, or send the form. Only your name and phone number are needed to start.

Talk to us

A call is the quickest way to know whether a vehicle is free on your dates. The form does the same thing if we are on the road.

Phone+91 63749 42976WhatsAppMessage usEmailniteshacars045@gmail.com
Find usNagercoil, Kanyakumari district, Tamil Nadu 629001

Check availability

Tell us when you need the car and we'll call you back to confirm. Only your name and phone number are required.

What happens after you send it

  1. We check what is free on your dates. If the vehicle you asked for is out, we will say so and tell you what else would suit rather than quietly substituting something.
  2. We call you back with the daily rate, the kilometre allowance, the extra-KM rate and the deposit. All four, before anything is agreed.
  3. If it suits you, we book it. Nothing is paid now — the deposit is taken at handover and comes back when the vehicle does.
  4. We bring the vehicle to you at the time agreed, anywhere in the district, with its papers in it.

What to have ready

Nothing, to enquire — a name and a number is enough. To take a vehicle you will need an original driving licence for the class you are hiring, one government photo ID, and the deposit. Visitors from abroad should bring a passport and an international driving permit alongside their home licence.

The full list of documents covers the exceptions, and the tariff has the figures for every vehicle if you would rather look before you ask.

A call is quicker than the form if your dates are close. We answer +91 63749 42976 and the same number on WhatsApp.

Where we deliver

Where we hire across Kanyakumari district

We serve the whole of Kanyakumari district in Tamil Nadu. Tell us where you are and we will sort out pickup.

Somewhere not listed? Ask us — if it is in the district, we can usually help. See every town we deliver to.

+ + diff --git a/docusaurus/docs/adding-a-css-modules-stylesheet.md b/docusaurus/docs/adding-a-css-modules-stylesheet.md deleted file mode 100644 index be169182848..00000000000 --- a/docusaurus/docs/adding-a-css-modules-stylesheet.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -id: adding-a-css-modules-stylesheet -title: Adding a CSS Modules Stylesheet -sidebar_label: Adding CSS Modules ---- - -> Note: this feature is available with `react-scripts@2.0.0` and higher. - -This project supports [CSS Modules](https://github.com/css-modules/css-modules) alongside regular stylesheets using the `[name].module.css` file naming convention. CSS Modules allows the scoping of CSS by automatically creating a unique classname of the format `[filename]\_[classname]\_\_[hash]`. - -> **Tip:** Should you want to preprocess a stylesheet with Sass then make sure to [follow the installation instructions](adding-a-sass-stylesheet.md) and then change the stylesheet file extension as follows: `[name].module.scss` or `[name].module.sass`. - -CSS Modules let you use the same CSS class name in different files without worrying about naming clashes. Learn more about CSS Modules [here](https://css-tricks.com/css-modules-part-1-need/). - -## `Button.module.css` - -```css -.error { - background-color: red; -} -``` - -## `another-stylesheet.css` - -```css -.error { - color: red; -} -``` - -## `Button.js` - -```js -import React, { Component } from 'react'; -import styles from './Button.module.css'; // Import css modules stylesheet as styles -import './another-stylesheet.css'; // Import regular stylesheet - -class Button extends Component { - render() { - // reference as a js object - return ; - } -} -``` - -## Result - -No clashes from other `.error` class names - -```html - - -``` - -**This is an optional feature.** Regular `` stylesheets and CSS files are fully supported. CSS Modules are turned on for files ending with the `.module.css` extension. diff --git a/docusaurus/docs/adding-a-router.md b/docusaurus/docs/adding-a-router.md deleted file mode 100644 index eae27dc501f..00000000000 --- a/docusaurus/docs/adding-a-router.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -id: adding-a-router -title: Adding a Router ---- - -Create React App doesn't prescribe a specific routing solution, but [React Router](https://reactrouter.com/) is the most popular one. - -To add it, run: - -```sh -npm install react-router-dom -``` - -Alternatively you may use `yarn`: - -```sh -yarn add react-router-dom -``` - -To try it, delete all the code in `src/App.js` and replace it with any of the examples on its website. The [Basic Example](https://reactrouter.com/docs/examples/basic) is a good place to get started. For more info on adding routes, check out [the React Router docs on adding routes](https://reactrouter.com/docs/getting-started/tutorial#add-some-routes). - -Note that [you may need to configure your production server to support client-side routing](deployment.md#serving-apps-with-client-side-routing) before deploying your app. diff --git a/docusaurus/docs/adding-a-sass-stylesheet.md b/docusaurus/docs/adding-a-sass-stylesheet.md deleted file mode 100644 index 5a287294155..00000000000 --- a/docusaurus/docs/adding-a-sass-stylesheet.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -id: adding-a-sass-stylesheet -title: Adding a Sass Stylesheet -sidebar_label: Adding Sass Stylesheets ---- - -> Note: this feature is available with `react-scripts@2.0.0` and higher. - -Generally, we recommend that you don’t reuse the same CSS classes across different components. For example, instead of using a `.Button` CSS class in `` and `` components, we recommend creating a ` - - ); - } -} - -export default App; -``` - -This will make `moduleA.js` and all its unique dependencies as a separate chunk that only loads after the user clicks the 'Load' button. For more information on the chunks that are created, see the [production build](production-build.md) section. - -You can also use it with `async` / `await` syntax if you prefer it. - -## With React Router - -If you are using React Router check out [this tutorial](https://reactjs.org/docs/code-splitting.html#route-based-code-splitting) - -Also check out the [Code Splitting](https://reactjs.org/docs/code-splitting.html) section in React documentation. diff --git a/docusaurus/docs/custom-templates.md b/docusaurus/docs/custom-templates.md deleted file mode 100644 index f1caf7568d7..00000000000 --- a/docusaurus/docs/custom-templates.md +++ /dev/null @@ -1,90 +0,0 @@ ---- -id: custom-templates -title: Custom Templates ---- - -> Note: this feature is available with `react-scripts@3.3.0` and higher. - -Custom Templates enable you to select a template to create your project from, while still retaining all of the features of Create React App. - -You'll notice that Custom Templates are always named in the format `cra-template-[template-name]`, however you only need to provide the `[template-name]` to the creation command. - -Scoped templates are also supported, under the name `@[scope-name]/cra-template` or `@[scope-name]/cra-template-[template-name]`, which can be installed via `@[scope]` and `@[scope]/[template-name]` respectively. - -```sh -npx create-react-app my-app --template [template-name] -``` - -## Finding custom templates - -We ship two templates by default: - -- [`cra-template`](https://github.com/facebook/create-react-app/tree/main/packages/cra-template) -- [`cra-template-typescript`](https://github.com/facebook/create-react-app/tree/main/packages/cra-template-typescript) - -However, you can find many great community templates by searching for ["cra-template-\*"](https://www.npmjs.com/search?q=cra-template-*) on npm. - -## Building a template - -If you're interested in building a custom template, first take a look at how we've built [`cra-template`](https://github.com/facebook/create-react-app/tree/main/packages/cra-template). - -A template must have the following structure: - -``` -cra-template-[template-name]/ - README.md (for npm) - template.json - package.json - template/ - README.md (for projects created from this template) - gitignore - public/ - index.html - src/ - index.js (or index.tsx) -``` - -### Testing a template - -To test a template locally, pass the file path to the directory of your template source using the `file:` prefix. - -```sh -npx create-react-app my-app --template file:../path/to/your/template/cra-template-[template-name] -``` - -### The `template` folder - -This folder is copied to the user's app directory as Create React App installs. During this process, the file `gitignore` is renamed to `.gitignore`. - -You can add whatever files you want in here, but you must have at least the files specified above. - -### The `template.json` file - -This is the configuration file for your template. As this is a new feature, more options will be added over time. For now, only a `package` key is supported. - -The `package` key lets you provide any keys/values that you want added to the new project's `package.json`, such as dependencies and any custom scripts that your template relies on. - -Below is an example `template.json` file: - -```json -{ - "package": { - "dependencies": { - "eslint-plugin-jsx-a11y": "^6.2.3", - "serve": "^11.2.0" - }, - "scripts": { - "serve": "serve -s build", - "build-and-serve": "npm run build && npm run serve" - }, - "eslintConfig": { - "extends": ["react-app", "plugin:jsx-a11y/recommended"], - "plugins": ["jsx-a11y"] - } - } -} -``` - -Any values you add for `"dependencies"` and `"scripts"` will be merged with the Create React App defaults. Values for any other keys will be used as-is, replacing any matching Create React App defaults. - -For convenience, we always replace `npm run` with `yarn` in your custom `"scripts"`, as well as in your `README` when projects are initialized with yarn. diff --git a/docusaurus/docs/debugging-tests.md b/docusaurus/docs/debugging-tests.md deleted file mode 100644 index 1261976343f..00000000000 --- a/docusaurus/docs/debugging-tests.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -id: debugging-tests -title: Debugging Tests -sidebar_label: Debugging Tests ---- - -There are various ways to setup a debugger for your Jest tests. We cover debugging in Chrome and [Visual Studio Code](https://code.visualstudio.com/). - -## Debugging Tests in Chrome - -Add the following to the `scripts` section in your project's `package.json` - -```json -"scripts": { - "test:debug": "react-scripts --inspect-brk test --runInBand --no-cache" - } -``` - -Place `debugger;` statements in any test and run: - -```sh -$ npm run test:debug -``` - -This will start running your Jest tests, but pause before executing to allow a debugger to attach to the process. - -Open the following in Chrome - -``` -about:inspect -``` - -After opening that link, the Chrome Developer Tools will be displayed. Select `inspect` on your process and a breakpoint will be set at the first line of the react script (this is done to give you time to open the developer tools and to prevent Jest from executing before you have time to do so). Click the button that looks like a "play" button in the upper right hand side of the screen to continue execution. When Jest executes the test that contains the debugger statement, execution will pause and you can examine the current scope and call stack. - -> Note: the --runInBand cli option makes sure Jest runs test in the same process rather than spawning processes for individual tests. Normally Jest parallelizes test runs across processes but it is hard to debug many processes at the same time. - -## Debugging Tests in Visual Studio Code - -Debugging Jest tests is supported out of the box for [Visual Studio Code](https://code.visualstudio.com). - -Use the following [`launch.json`](https://code.visualstudio.com/docs/editor/debugging#_launch-configurations) configuration file: - -```json -{ - "version": "0.2.0", - "configurations": [ - { - "name": "Debug CRA Tests", - "type": "node", - "request": "launch", - "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/react-scripts", - "args": ["test", "--runInBand", "--no-cache", "--watchAll=false"], - "cwd": "${workspaceRoot}", - "protocol": "inspector", - "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen", - "env": { "CI": "true" }, - "disableOptimisticBPs": true - } - ] -} -``` diff --git a/docusaurus/docs/deployment.md b/docusaurus/docs/deployment.md deleted file mode 100644 index 14a29997761..00000000000 --- a/docusaurus/docs/deployment.md +++ /dev/null @@ -1,508 +0,0 @@ ---- -id: deployment -title: Deployment -sidebar_label: Deployment ---- - -`npm run build` creates a `build` directory with a production build of your app. Set up your favorite HTTP server so that a visitor to your site is served `index.html`, and requests to static paths like `/static/js/main..js` are served with the contents of the `/static/js/main..js` file. For more information see the [production build](production-build.md) section. - -## Static Server - -For environments using [Node](https://nodejs.org/), the easiest way to handle this would be to install [serve](https://github.com/vercel/serve) and let it handle the rest: - -```sh -npm install -g serve -serve -s build -``` - -The last command shown above will serve your static site on the port **3000**. Like many of [serve](https://github.com/vercel/serve)’s internal settings, the port can be adjusted using the `-l` or `--listen` flags: - -```sh -serve -s build -l 4000 -``` - -Run this command to get a full list of the options available: - -```sh -serve -h -``` - -## Other Solutions - -You don’t necessarily need a static server in order to run a Create React App project in production. It also works well when integrated into an existing server side app. - -Here’s a programmatic example using [Node](https://nodejs.org/) and [Express](https://expressjs.com/): - -```javascript -const express = require('express'); -const path = require('path'); -const app = express(); - -app.use(express.static(path.join(__dirname, 'build'))); - -app.get('/', function (req, res) { - res.sendFile(path.join(__dirname, 'build', 'index.html')); -}); - -app.listen(9000); -``` - -The choice of your server software isn’t important either. Since Create React App is completely platform-agnostic, there’s no need to explicitly use Node. - -The `build` folder with static assets is the only output produced by Create React App. - -However this is not quite enough if you use client-side routing. Read the next section if you want to support URLs like `/todos/42` in your single-page app. - -## Serving Apps with Client-Side Routing - -If you use routers that use the HTML5 [`pushState` history API](https://developer.mozilla.org/en-US/docs/Web/API/History_API#Adding_and_modifying_history_entries) under the hood (for example, [React Router](https://github.com/ReactTraining/react-router) with `browserHistory`), many static file servers will fail. For example, if you used React Router with a route for `/todos/42`, the development server will respond to `localhost:3000/todos/42` properly, but an Express serving a production build as above will not. - -This is because when there is a fresh page load for a `/todos/42`, the server looks for the file `build/todos/42` and does not find it. The server needs to be configured to respond to a request to `/todos/42` by serving `index.html`. For example, we can amend our Express example above to serve `index.html` for any unknown paths: - -```diff - app.use(express.static(path.join(__dirname, 'build'))); - --app.get('/', function (req, res) { -+app.get('/*', function (req, res) { - res.sendFile(path.join(__dirname, 'build', 'index.html')); - }); -``` - -If you’re using [Apache HTTP Server](https://httpd.apache.org/), you need to create a `.htaccess` file in the `public` folder that looks like this: - -``` - Options -MultiViews - RewriteEngine On - RewriteCond %{REQUEST_FILENAME} !-f - RewriteRule ^ index.html [QSA,L] -``` - -It will get copied to the `build` folder when you run `npm run build`. - -If you’re using [Apache Tomcat](https://tomcat.apache.org/), you need to follow [this Stack Overflow answer](https://stackoverflow.com/a/41249464/4878474). - -Now requests to `/todos/42` will be handled correctly both in development and in production. - -On a production build, and when you've [opted-in](making-a-progressive-web-app.md#why-opt-in), -a [service worker](https://developers.google.com/web/fundamentals/primers/service-workers/) will automatically handle all navigation requests, like for -`/todos/42`, by serving the cached copy of your `index.html`. This -service worker navigation routing can be configured or disabled by -[`eject`ing](available-scripts.md#npm-run-eject) and then modifying the -[`navigateFallback`](https://github.com/GoogleChrome/sw-precache#navigatefallback-string) -and [`navigateFallbackWhitelist`](https://github.com/GoogleChrome/sw-precache#navigatefallbackwhitelist-arrayregexp) -options of the `SWPrecachePlugin` configuration. - -When users install your app to the homescreen of their device the default configuration will make a shortcut to `/index.html`. This may not work for client-side routers which expect the app to be served from `/`. Edit the web app manifest at `public/manifest.json` and change `start_url` to match the required URL scheme, for example: - -```js - "start_url": ".", -``` - -## Building for Relative Paths - -By default, Create React App produces a build assuming your app is hosted at the server root. - -To override this, specify the `homepage` in your `package.json`, for example: - -```js - "homepage": "http://mywebsite.com/relativepath", -``` - -This will let Create React App correctly infer the root path to use in the generated HTML file. - -**Note**: If you are using `react-router@^4`, you can root ``s using the `basename` prop on any ``. - -More information [here](https://reacttraining.com/react-router/web/api/BrowserRouter/basename-string). - -For example: - -```js - - // renders -``` - -### Serving the Same Build from Different Paths - -> Note: this feature is available with `react-scripts@0.9.0` and higher. - -If you are not using the HTML5 `pushState` history API or not using client-side routing at all, it is unnecessary to specify the URL from which your app will be served. Instead, you can put this in your `package.json`: - -```js - "homepage": ".", -``` - -This will make sure that all the asset paths are relative to `index.html`. You will then be able to move your app from `http://mywebsite.com` to `http://mywebsite.com/relativepath` or even `http://mywebsite.com/relative/path` without having to rebuild it. - -## Customizing Environment Variables for Arbitrary Build Environments - -You can create an arbitrary build environment by creating a custom `.env` file and loading it using [env-cmd](https://www.npmjs.com/package/env-cmd). - -For example, to create a build environment for a staging environment: - -1. Create a file called `.env.staging` -1. Set environment variables as you would any other `.env` file (e.g. `REACT_APP_API_URL=http://api-staging.example.com`) -1. Install [env-cmd](https://www.npmjs.com/package/env-cmd) - ```sh - $ npm install env-cmd --save - $ # or - $ yarn add env-cmd - ``` -1. Add a new script to your `package.json`, building with your new environment: - ```json - { - "scripts": { - "build:staging": "env-cmd -f .env.staging npm run build" - } - } - ``` - -Now you can run `npm run build:staging` to build with the staging environment config. -You can specify other environments in the same way. - -Variables in `.env.production` will be used as fallback because `NODE_ENV` will always be set to `production` for a build. - -## [AWS Amplify](https://console.amplify.aws) - -The AWS Amplify Console provides continuous deployment and hosting for modern web apps (single page apps and static site generators) with serverless backends. The Amplify Console offers globally available CDNs, custom domain setup, feature branch deployments, and password protection. - -1. Login to the Amplify Console [here](https://console.aws.amazon.com/amplify/home). -1. Connect your Create React App repo and pick a branch. If you're looking for a Create React App+Amplify starter, try the [create-react-app-auth-amplify starter](https://github.com/swaminator/create-react-app-auth-amplify) that demonstrates setting up auth in 10 minutes with Create React App. -1. The Amplify Console automatically detects the build settings. Choose Next. -1. Choose _Save and deploy_. - -If the build succeeds, the app is deployed and hosted on a global CDN with an amplifyapp.com domain. You can now continuously deploy changes to your frontend or backend. Continuous deployment allows developers to deploy updates to their frontend and backend on every code commit to their Git repository. - -## [Azure](https://azure.microsoft.com/) - -Azure Static Web Apps creates an automated build and deploy pipeline for your React app powered by GitHub Actions. Applications are geo-distributed by default with multiple points of presence. PR's are built automatically for staging environment previews. - -1. Create a new Static Web App [here](https://ms.portal.azure.com/#create/Microsoft.StaticApp). -1. Add in the details and connect to your GitHub repo. -1. Make sure the build folder is set correctly on the "build" tab and create the resource. - -Azure Static Web Apps will automatically configure a GitHub Action in your repo and begin the deployment. - -See the [Azure Static Web Apps documentation](https://aka.ms/swadocs) for more information on routing, APIs, authentication and authorization, custom domains and more. - -## [Firebase](https://firebase.google.com/) - -Install the Firebase CLI if you haven’t already by running `npm install -g firebase-tools`. Sign up for a [Firebase account](https://console.firebase.google.com/) and create a new project. Run `firebase login` and login with your previous created Firebase account. - -Then run the `firebase init` command from your project’s root. You need to choose the **Hosting: Configure and deploy Firebase Hosting sites** and choose the Firebase project you created in the previous step. You will need to agree with `database.rules.json` being created, choose `build` as the public directory, and also agree to **Configure as a single-page app** by replying with `y`. - -```sh - === Project Setup - - First, let's associate this project directory with a Firebase project. - You can create multiple project aliases by running firebase use --add, - but for now we'll set up a default project. - - ? What Firebase project do you want to associate as default? Example app (example-app-fd690) - - === Database Setup - - Firebase Realtime Database Rules allow you to define how your data should be - structured and when your data can be read from and written to. - - ? What file should be used for Database Rules? database.rules.json - ✔ Database Rules for example-app-fd690 have been downloaded to database.rules.json. - Future modifications to database.rules.json will update Database Rules when you run - firebase deploy. - - === Hosting Setup - - Your public directory is the folder (relative to your project directory) that - will contain Hosting assets to uploaded with firebase deploy. If you - have a build process for your assets, use your build's output directory. - - ? What do you want to use as your public directory? build - ? Configure as a single-page app (rewrite all urls to /index.html)? Yes - ✔ Wrote build/index.html - - i Writing configuration info to firebase.json... - i Writing project information to .firebaserc... - - ✔ Firebase initialization complete! -``` - -IMPORTANT: you need to set proper HTTP caching headers for `service-worker.js` file in `firebase.json` file or you will not be able to see changes after first deployment ([issue #2440](https://github.com/facebook/create-react-app/issues/2440)). It should be added inside `"hosting"` key like next: - -```json -{ - "hosting": { - ... - "headers": [ - {"source": "/service-worker.js", "headers": [{"key": "Cache-Control", "value": "no-cache"}]} - ] - ... -``` - -Now, after you create a production build with `npm run build`, you can deploy it by running `firebase deploy`. - -```sh - === Deploying to 'example-app-fd690'... - - i deploying database, hosting - ✔ database: rules ready to deploy. - i hosting: preparing build directory for upload... - Uploading: [============================== ] 75%✔ hosting: build folder uploaded successfully - ✔ hosting: 8 files uploaded successfully - i starting release process (may take several minutes)... - - ✔ Deploy complete! - - Project Console: https://console.firebase.google.com/project/example-app-fd690/overview - Hosting URL: https://example-app-fd690.firebaseapp.com -``` - -For more information see [Firebase Hosting](https://firebase.google.com/docs/hosting). - -## [GitHub Pages](https://pages.github.com/) - -> Note: this feature is available with `react-scripts@0.2.0` and higher. - -### Step 1: Add `homepage` to `package.json` - -**The step below is important!**
- -**If you skip it, your app will not deploy correctly.** - -Open your `package.json` and add a `homepage` field for your project: - -```json - "homepage": "https://myusername.github.io/my-app", -``` - -or for a GitHub user page: - -```json - "homepage": "https://myusername.github.io", -``` - -or for a custom domain page: - -```json - "homepage": "https://mywebsite.com", -``` - -Create React App uses the `homepage` field to determine the root URL in the built HTML file. - -### Step 2: Install `gh-pages` and add `deploy` to `scripts` in `package.json` - -Now, whenever you run `npm run build`, you will see a cheat sheet with instructions on how to deploy to GitHub Pages. - -To publish it at [https://myusername.github.io/my-app](https://myusername.github.io/my-app), run: - -```sh -npm install --save gh-pages -``` - -Alternatively you may use `yarn`: - -```sh -yarn add gh-pages -``` - -Add the following scripts in your `package.json`: - -```diff - "scripts": { -+ "predeploy": "npm run build", -+ "deploy": "gh-pages -d build", - "start": "react-scripts start", - "build": "react-scripts build", -``` - -The `predeploy` script will run automatically before `deploy` is run. - -If you are deploying to a GitHub user page instead of a project page you'll need to make one -additional modification: - -1. Tweak your `package.json` scripts to push deployments to **main**: - -```diff - "scripts": { - "predeploy": "npm run build", -- "deploy": "gh-pages -d build", -+ "deploy": "gh-pages -b main -d build", -``` - -### Step 3: Deploy the site by running `npm run deploy` - -Then run: - -```sh -npm run deploy -``` - -### Step 4: For a project page, ensure your project’s settings use `gh-pages` - -Finally, make sure **GitHub Pages** option in your GitHub project settings is set to use the `gh-pages` branch: - -gh-pages branch setting - -### Step 5: Optionally, configure the domain - -You can configure a custom domain with GitHub Pages by adding a `CNAME` file to the `public/` folder. - -Your CNAME file should look like this: - -``` -mywebsite.com -``` - -### Notes on client-side routing - -GitHub Pages doesn’t support routers that use the HTML5 `pushState` history API under the hood (for example, React Router using `browserHistory`). This is because when there is a fresh page load for a url like `http://user.github.io/todomvc/todos/42`, where `/todos/42` is a frontend route, the GitHub Pages server returns 404 because it knows nothing of `/todos/42`. If you want to add a router to a project hosted on GitHub Pages, here are a couple of solutions: - -- You could switch from using HTML5 history API to routing with hashes. If you use React Router, you can switch to `hashHistory` for this effect, but the URL will be longer and more verbose (for example, `http://user.github.io/todomvc/#/todos/42?_k=yknaj`). [Read more](https://reacttraining.com/react-router/web/api/Router) about different history implementations in React Router. -- Alternatively, you can use a trick to teach GitHub Pages to handle 404s by redirecting to your `index.html` page with a custom redirect parameter. You would need to add a `404.html` file with the redirection code to the `build` folder before deploying your project, and you’ll need to add code handling the redirect parameter to `index.html`. You can find a detailed explanation of this technique [in this guide](https://github.com/rafrex/spa-github-pages). - -### Troubleshooting - -#### "/dev/tty: No such a device or address" - -If, when deploying, you get `/dev/tty: No such a device or address` or a similar error, try the following: - -1. Create a new [Personal Access Token](https://github.com/settings/tokens) -2. `git remote set-url origin https://:@github.com//` . -3. Try `npm run deploy` again - -#### "Cannot read property 'email' of null" - -If, when deploying, you get `Cannot read property 'email' of null`, try the following: - -1. `git config --global user.name ''` -2. `git config --global user.email ''` -3. Try `npm run deploy` again - -## [Heroku](https://www.heroku.com/) - -Use the [Heroku Buildpack for Create React App](https://github.com/heroku/heroku-buildpack-nodejs). - -You can find instructions in [Deploying React with Zero Configuration](https://blog.heroku.com/deploying-react-with-zero-configuration). - -### Resolving Heroku Deployment Errors - -Sometimes `npm run build` works locally but fails during deploy via Heroku. Following are the most common cases. - -#### "Module not found: Error: Cannot resolve 'file' or 'directory'" - -If you get something like this: - -``` -remote: Failed to create a production build. Reason: -remote: Module not found: Error: Cannot resolve 'file' or 'directory' -MyDirectory in /tmp/build_1234/src -``` - -It means you need to ensure that the lettercase of the file or directory you `import` matches the one you see on your filesystem or on GitHub. - -This is important because Linux (the operating system used by Heroku) is case sensitive. So `MyDirectory` and `mydirectory` are two distinct directories and thus, even though the project builds locally, the difference in case breaks the `import` statements on Heroku remotes. - -#### "Could not find a required file." - -If you exclude or ignore necessary files from the package you will see a error similar this one: - -``` -remote: Could not find a required file. -remote: Name: `index.html` -remote: Searched in: /tmp/build_a2875fc163b209225122d68916f1d4df/public -remote: -remote: npm ERR! Linux 3.13.0-105-generic -remote: npm ERR! argv "/tmp/build_a2875fc163b209225122d68916f1d4df/.heroku/node/bin/node" "/tmp/build_a2875fc163b209225122d68916f1d4df/.heroku/node/bin/npm" "run" "build" -``` - -In this case, ensure that the file is there with the proper lettercase and that’s not ignored on your local `.gitignore` or `~/.gitignore_global`. - -## [Netlify](https://www.netlify.com/) - -**To do a manual deploy to Netlify’s CDN:** - -```sh -npm install netlify-cli -g -netlify deploy -``` - -Choose `build` as the path to deploy. - -**To setup continuous delivery:** - -With this setup Netlify will build and deploy when you push to git or open a pull request: - -1. [Start a new netlify project](https://app.netlify.com/signup) -2. Pick your Git hosting service and select your repository -3. Click `Build your site` - -**Support for client-side routing:** - -To support `pushState`, make sure to create a `public/_redirects` file with the following rewrite rules: - -``` -/* /index.html 200 -``` - -When you build the project, Create React App will place the `public` folder contents into the build output. - -## [Vercel](https://vercel.com) - -[Vercel](https://vercel.com/home) is a cloud platform that enables developers to host Jamstack websites and web services that deploy instantly, scale automatically, and requires no supervision, all with zero configuration. They provide a global edge network, SSL encryption, asset compression, cache invalidation, and more. - -### Step 1: Deploying your React project to Vercel - -To deploy your React project with a [Vercel for Git Integration](https://vercel.com/docs/git-integrations), make sure it has been pushed to a Git repository. - -Import the project into Vercel using the [Import Flow](https://vercel.com/import/git). During the import, you will find all relevant [options](https://vercel.com/docs/build-step#build-&-development-settings) preconfigured for you with the ability to change as needed. - -After your project has been imported, all subsequent pushes to branches will generate [Preview Deployments](https://vercel.com/docs/platform/deployments#preview), and all changes made to the [Production Branch](https://vercel.com/docs/git-integrations#production-branch) (commonly "master" or "main") will result in a [Production Deployment](https://vercel.com/docs/platform/deployments#production). - -Once deployed, you will get a URL to see your app live, such as the following: https://create-react-app-example.vercel.app/. - -### Step 2 (optional): Using a Custom Domain - -If you want to use a Custom Domain with your Vercel deployment, you can **Add** or **Transfer in** your domain via your Vercel [account Domain settings.](https://vercel.com/dashboard/domains) - -To add your domain to your project, navigate to your [Project](https://vercel.com/docs/platform/projects) from the Vercel Dashboard. Once you have selected your project, click on the "Settings" tab, then select the **Domains** menu item. From your projects **Domain** page, enter the domain you wish to add to your project. - -Once the domain has been added, you will be presented with different methods for configuring it. - -### Deploying a fresh React project - -You can deploy a fresh React project, with a Git repository set up for you, with the following Deploy Button: - -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/git?s=https%3A%2F%2Fgithub.com%2Fvercel%2Fvercel%2Ftree%2Fmaster%2Fexamples%2Fcreate-react-app) - -### Vercel References: - -- [Example Source](https://github.com/vercel/vercel/tree/master/examples/create-react-app) -- [Official Vercel Guide](https://vercel.com/guides/deploying-react-with-vercel-cra) -- [Vercel Deployment Docs](https://vercel.com/docs) -- [Vercel Custom Domain Docs](https://vercel.com/docs/custom-domains) - -## [Render](https://render.com) - -Render offers free [static site](https://render.com/docs/static-sites) hosting with fully managed SSL, a global CDN and continuous auto deploys from GitHub. - -Deploy your app in only a few minutes by following the [Create React App deployment guide](https://render.com/docs/deploy-create-react-app). - -Use invite code `cra` to sign up or use [this link](https://render.com/i/cra). - -## [S3](https://aws.amazon.com/s3) and [CloudFront](https://aws.amazon.com/cloudfront/) - -See this [blog post](https://medium.com/@omgwtfmarc/deploying-create-react-app-to-s3-or-cloudfront-48dae4ce0af) on how to deploy your React app to Amazon Web Services S3 and CloudFront. If you are looking to add a custom domain, HTTPS and continuous deployment see this [blog post](https://medium.com/dailyjs/a-guide-to-deploying-your-react-app-with-aws-s3-including-https-a-custom-domain-a-cdn-and-58245251f081). - -## [Surge](https://surge.sh/) - -Install the Surge CLI if you haven’t already by running `npm install -g surge`. Run the `surge` command and log in you or create a new account. - -When asked about the project path, make sure to specify the `build` folder, for example: - -```sh - project path: /path/to/project/build -``` - -Note that in order to support routers that use HTML5 `pushState` API, you may want to rename the `index.html` in your build folder to `200.html` before deploying to Surge. This [ensures that every URL falls back to that file](https://surge.sh/help/adding-a-200-page-for-client-side-routing). - -## Publishing Components To npm - -Create React App doesn't provide any built-in functionality to publish a component to npm. If you're ready to extract a component from your project so other people can use it, we recommend moving it to a separate directory outside of your project and then using a tool like [nwb](https://github.com/insin/nwb#react-components-and-libraries) to prepare it for publishing. diff --git a/docusaurus/docs/developing-components-in-isolation.md b/docusaurus/docs/developing-components-in-isolation.md deleted file mode 100644 index a0fce33f02d..00000000000 --- a/docusaurus/docs/developing-components-in-isolation.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -id: developing-components-in-isolation -title: Developing Components in Isolation ---- - -Usually, in an app, you have a lot of UI components, and each of them has many different states. -For an example, a basic button component could have the following states: - -- In a regular state, with a text label. -- In the disabled mode. -- In a loading state. - -Usually, it’s hard to see these states without running a sample app or some examples. - -Create React App doesn’t include any tools for this by default, but you can add [Storybook for React](https://storybook.js.org) ([source](https://github.com/storybooks/storybook)) or [React Styleguidist](https://react-styleguidist.js.org/) ([source](https://github.com/styleguidist/react-styleguidist)) to your project. **These are third-party tools that let you develop components and see all their states in isolation from your app**. - -![Storybook for React Demo](https://raw.githubusercontent.com/storybookjs/storybook/next/docs/get-started/storybook-third-party-docs.gif) - -You can also deploy your Storybook or style guide as a static app. This way, everyone in your team can view and review different states of UI components without starting a backend server or creating an account in your app. - -## Getting Started with Storybook - -Storybook is a development environment for React UI components. It allows you to browse a component library, view the different states of each component, and interactively develop and test components. - -Run the following command inside your app’s directory: - -```sh -npx sb init -``` - -After that, follow the instructions on the screen. - -Learn more about React Storybook: - -- [Learn Storybook (tutorial)](https://storybook.js.org/tutorials/) -- [Documentation](https://storybook.js.org/docs/react/get-started/introduction) -- [GitHub Repo](https://github.com/storybooks/storybook) -- [Snapshot Testing UI](https://github.com/storybooks/storybook/tree/master/addons/storyshots) with Storybook + addon/storyshot - -## Getting Started with Styleguidist - -Styleguidist combines a style guide, where all your components are presented on a single page with their props documentation and usage examples, with an environment for developing components in isolation, similar to Storybook. In Styleguidist you write examples in Markdown, where each code snippet is rendered as a live editable playground. - -First, install Styleguidist: - -```sh -npm install --save react-styleguidist -``` - -Alternatively you may use `yarn`: - -```sh -yarn add react-styleguidist -``` - -Then, add these scripts to your `package.json`: - -```diff - "scripts": { -+ "styleguide": "styleguidist server", -+ "styleguide:build": "styleguidist build", - "start": "react-scripts start", -``` - -Then, run the following command inside your app’s directory: - -```sh -npm run styleguide -``` - -After that, follow the instructions on the screen. - -Learn more about React Styleguidist: - -- [GitHub Repo](https://github.com/styleguidist/react-styleguidist) -- [Documentation](https://react-styleguidist.js.org/docs/getting-started.html) diff --git a/docusaurus/docs/documentation-intro.md b/docusaurus/docs/documentation-intro.md deleted file mode 100644 index aee991f0f40..00000000000 --- a/docusaurus/docs/documentation-intro.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -id: documentation-intro -title: About the Documentation -sidebar_label: About Docs ---- - -Welcome to the Create React App documentation! - -## Navigation - -You can find different topics in the table of contents. On desktop, you should see it in the left sidebar. On mobile, you should see it after pressing an icon with arrows in the top left corner. - -## Looking for React Docs? - -The documentation for React itself is located on a separate website: **[reactjs.org](https://reactjs.org/)**. - -This website is only about Create React App. - -## Something Missing? - -If you have ideas for more “How To” recipes that should be on this page, [let us know](https://github.com/facebook/create-react-app/issues) or [contribute some!](https://github.com/facebook/create-react-app/tree/main/docusaurus/docs) - -## Feedback - -We are always open to [your feedback](https://github.com/facebook/create-react-app/issues). diff --git a/docusaurus/docs/fetching-data-with-ajax-requests.md b/docusaurus/docs/fetching-data-with-ajax-requests.md deleted file mode 100644 index 43bcc902f67..00000000000 --- a/docusaurus/docs/fetching-data-with-ajax-requests.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -id: fetching-data-with-ajax-requests -title: Fetching Data with AJAX Requests -sidebar_label: Fetching Data ---- - -React doesn't prescribe a specific approach to data fetching, but people commonly use either a library like [axios](https://github.com/axios/axios) or the [`fetch()` API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) provided by the browser. - -The global `fetch` function allows you to make AJAX requests. It takes in a URL as an input and returns a `Promise` that resolves to a `Response` object. You can find more information about `fetch` [here](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch). - -A Promise represents the eventual result of an asynchronous operation, you can find more information about Promises [here](https://www.promisejs.org/) and [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). Both axios and `fetch()` use Promises under the hood. You can also use the [`async / await`](https://davidwalsh.name/async-await) syntax to reduce the callback nesting. - -Make sure the [`fetch()` API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) and [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) are available in your target audience's browsers. -For example, support in Internet Explorer requires a [polyfill](https://github.com/facebook/create-react-app/blob/main/packages/react-app-polyfill/README.md). - -You can learn more about making AJAX requests from React components in [the FAQ entry on the React website](https://reactjs.org/docs/faq-ajax.html). diff --git a/docusaurus/docs/folder-structure.md b/docusaurus/docs/folder-structure.md deleted file mode 100644 index f135b635164..00000000000 --- a/docusaurus/docs/folder-structure.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -id: folder-structure -title: Folder Structure ---- - -After creation, your project should look like this: - -``` -my-app/ - README.md - node_modules/ - package.json - public/ - index.html - favicon.ico - src/ - App.css - App.js - App.test.js - index.css - index.js - logo.svg -``` - -For the project to build, **these files must exist with exact filenames**: - -- `public/index.html` is the page template; -- `src/index.js` is the JavaScript entry point. - -You can delete or rename the other files. - -You may create subdirectories inside `src`. For faster rebuilds, only files inside `src` are processed by webpack. You need to **put any JS and CSS files inside `src`**, otherwise webpack won’t see them. - -Only files inside `public` can be used from `public/index.html`. Read instructions below for using assets from JavaScript and HTML. - -You can, however, create more top-level directories. They will not be included in the production build so you can use them for things like documentation. - -If you have Git installed and your project is not part of a larger repository, then a new repository will be initialized resulting in an additional top-level `.git` directory. diff --git a/docusaurus/docs/getting-started.md b/docusaurus/docs/getting-started.md deleted file mode 100644 index ef7da3006f0..00000000000 --- a/docusaurus/docs/getting-started.md +++ /dev/null @@ -1,162 +0,0 @@ ---- -id: getting-started -title: Getting Started ---- - -Create React App is an officially supported way to create single-page React -applications. It offers a modern build setup with no configuration. - -## Quick Start - -```sh -npx create-react-app my-app -cd my-app -npm start -``` - -> If you've previously installed `create-react-app` globally via `npm install -g create-react-app`, we recommend you uninstall the package using `npm uninstall -g create-react-app` or `yarn global remove create-react-app` to ensure that `npx` always uses the latest version. - -_([npx](https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b) comes with npm 5.2+ and higher, see [instructions for older npm versions](https://gist.github.com/gaearon/4064d3c23a77c74a3614c498a8bb1c5f))_ - -Then open [http://localhost:3000/](http://localhost:3000/) to see your app. - -When you’re ready to deploy to production, create a minified bundle with `npm run build`. - -

-npm start -

- -### Get Started Immediately - -You **don’t** need to install or configure tools like webpack or Babel. They are preconfigured and hidden so that you can focus on the code. - -Create a project, and you’re good to go. - -## Creating an App - -**You’ll need to have Node >= 14 on your local development machine** (but it’s not required on the server). You can use [nvm](https://github.com/creationix/nvm#installation) (macOS/Linux) or [nvm-windows](https://github.com/coreybutler/nvm-windows#node-version-manager-nvm-for-windows) to switch Node versions between different projects. - -To create a new app, you may choose one of the following methods: - -### npx - -```sh -npx create-react-app@latest my-app -``` - -_([npx](https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b) comes with npm 5.2+ and higher, see [instructions for older npm versions](https://gist.github.com/gaearon/4064d3c23a77c74a3614c498a8bb1c5f))_ - -### npm - -```sh -npm init react-app my-app -``` - -_`npm init ` is available in npm 6+_ - -### Yarn - -```sh -yarn create react-app my-app -``` - -_`yarn create` is available in Yarn 0.25+_ - -### Selecting a template - -You can now optionally start a new app from a template by appending `--template [template-name]` to the creation command. - -If you don't select a template, we'll create your project with our base template. - -Templates are always named in the format `cra-template-[template-name]`, however you only need to provide the `[template-name]` to the creation command. - -```sh -npx create-react-app my-app --template [template-name] -``` - -> You can find a list of available templates by searching for ["cra-template-\*"](https://www.npmjs.com/search?q=cra-template-*) on npm. - -Our [Custom Templates](custom-templates.md) documentation describes how you can build your own template. - -#### Creating a TypeScript app - -You can start a new TypeScript app using templates. To use our provided TypeScript template, append `--template typescript` to the creation command. - -```sh -npx create-react-app my-app --template typescript -``` - -If you already have a project and would like to add TypeScript, see our [Adding TypeScript](adding-typescript.md) documentation. - -### Selecting a package manager - -When you create a new app, the CLI will use [npm](https://docs.npmjs.com) or [Yarn](https://yarnpkg.com/) to install dependencies, depending on which tool you use to run `create-react-app`. For example: - -```sh -# Run this to use npm -npx create-react-app my-app -# Or run this to use yarn -yarn create react-app my-app -``` - -## Output - -Running any of these commands will create a directory called `my-app` inside the current folder. Inside that directory, it will generate the initial project structure and install the transitive dependencies: - -``` -my-app -├── README.md -├── node_modules -├── package.json -├── .gitignore -├── public -│ ├── favicon.ico -│ ├── index.html -│ ├── logo192.png -│ ├── logo512.png -│ ├── manifest.json -│ └── robots.txt -└── src - ├── App.css - ├── App.js - ├── App.test.js - ├── index.css - ├── index.js - ├── logo.svg - ├── serviceWorker.js - └── setupTests.js -``` - -No configuration or complicated folder structures, only the files you need to build your app. Once the installation is done, you can open your project folder: - -```sh -cd my-app -``` - -## Scripts - -Inside the newly created project, you can run some built-in commands: - -### `npm start` or `yarn start` - -Runs the app in development mode. Open [http://localhost:3000](http://localhost:3000) to view it in the browser. - -The page will automatically reload if you make changes to the code. You will see the build errors and lint warnings in the console. - -

-Build errors -

- -### `npm test` or `yarn test` - -Runs the test watcher in an interactive mode. By default, runs tests related to files changed since the last commit. - -[Read more about testing](running-tests.md). - -### `npm run build` or `yarn build` - -Builds the app for production to the `build` folder. It correctly bundles React in production mode and optimizes the build for the best performance. - -The build is minified and the filenames include the hashes. - -Your app is ready to be deployed. diff --git a/docusaurus/docs/importing-a-component.md b/docusaurus/docs/importing-a-component.md deleted file mode 100644 index 7f23cb9c7d5..00000000000 --- a/docusaurus/docs/importing-a-component.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -id: importing-a-component -title: Importing a Component ---- - -This project setup supports ES6 modules thanks to webpack. - -While you can still use `require()` and `module.exports`, we encourage you to use [`import` and `export`](https://exploringjs.com/es6/ch_modules.html) instead. - -For example: - -## `Button.js` - -```js -import React, { Component } from 'react'; - -class Button extends Component { - render() { - // ... - } -} - -export default Button; // Don’t forget to use export default! -``` - -## `DangerButton.js` - -```js -import React, { Component } from 'react'; -import Button from './Button'; // Import a component from another file - -class DangerButton extends Component { - render() { - return
Self-drive rental car in Nagercoil, Kanyakumari district

Nagercoil & Kanyakumari district

Self drive car & bike rental in Nagercoiland across Kanyakumari district

Take the wheel. We'll handle the rest.

Self drive cars, bike rental, wedding cars and tourist vehicles with a driver — across Nagercoil, Marthandam, Colachel and the whole of Kanyakumari district. Transparent rates and a deposit you get back.

Check a date

Tell us when you need it and we will call you back.

WhatsApp
  • Delivered to your doorstep
  • Zero hidden charges
  • Day, week or month

Why hire from us

Straight answers, and a vehicle that turns up

Hiring a vehicle should not be the complicated part of a trip. We tell you the rate, the limits and the deposit before you take the car, and we bring it to you wherever you are in the district.

Every vehicle carries three numbers before you take it: the daily rate, the kilometres included each day, and what anything beyond that costs. The deposit is stated too, and it comes back when the car does. Nothing is discovered at the end.

Booking, start to finish

How it works

  1. Pick your car

    Browse the fleet and choose what suits the trip — a hatchback for the city, an SUV for the highway.

  2. Send an enquiry

    Tell us your dates. We'll call back to confirm the car is free and agree the rate.

  3. Collect and drive

    Bring a valid licence and ID. Pay the deposit, take the keys, and the car is yours.

  4. Bring it back

    Drop it back at the time agreed. We check it over, and the deposit comes back the same day.

Deposits are refundable. It is returned after the car comes back, less any extra-KM charges or damage. KM limits and extra-KM rates are listed on every car above, so there is nothing to discover later.

Where we deliver

Where we hire across Kanyakumari district

We serve the whole of Kanyakumari district in Tamil Nadu. Tell us where you are and we will sort out pickup.

Somewhere not listed? Ask us — if it is in the district, we can usually help. See every town we deliver to.

Where people go

The coast, the temples and the waterfalls are all an easy drive from Nagercoil. Take the car and go at your own pace.

View all 12 places

Coast

Vattakottai Fort

A small granite fort right on the shore about six kilometres up the coast from Kanyakumari, with the sea on one side and the hills behind. Half an hour is enough, and the drive there along the coast road is half the reason to go.

Directionsto Vattakottai Fort, opens Google Maps in a new tab

Heritage

Padmanabhapuram Palace

The old wooden palace of the Travancore kings at Thuckalay, and the largest of its kind anywhere. Bigger inside than it looks and worth ninety minutes. It shuts earlier in the afternoon than most people expect, so check before setting off.

Directionsto Padmanabhapuram Palace, opens Google Maps in a new tab

Questions we are asked most

If yours is not here, call or send a WhatsApp message and we will answer it straight.

Do I need a deposit to take a self drive car?

Yes, a refundable security deposit, and you get it back when the vehicle comes back in the same condition. The amount depends on the vehicle and we tell you the figure before you book, not when you arrive.

What documents do I need to rent a car without a driver?

Your original driving licence and one photo ID. We take a copy at handover. For visitors from abroad, bring your passport and an international driving permit alongside your home licence.

Is there a kilometre limit, and what happens if I go over it?

Every vehicle comes with a daily kilometre allowance, and anything beyond it is charged at a fixed rate per kilometre. Both numbers are on the tariff page and on your booking, so there is nothing to work out afterwards.

Can you deliver the car to Nagercoil railway station or the airport?

We deliver and collect across Kanyakumari district — Nagercoil, Kanyakumari, Marthandam, Colachel, Thuckalay and the towns around them — including the railway station and the bus stand. Tell us where you land and when, and we will say what we can do.

Can I take the car into Kerala?

Usually yes — Trivandrum is an hour away and a lot of our hires cross the border. Tell us where you are going when you book, so the paperwork is right before you set off.

Do you rent two-wheelers as well as cars?

Yes. Scooters and motorcycles by the hour, the day or the week, on the same terms as the cars. A two-wheeler is the easier way around Nagercoil and out to the coast.

Is a monthly rental cheaper than paying by the day?

Considerably. Weekly, fortnightly and monthly rates all bring the daily figure down, which is what most people on a work posting or a long stay take. Ask us for a monthly quote with the kilometres you expect to drive.

Do you provide a car with a driver for sightseeing?

Yes. Tourist vehicles come with a driver who knows the district, for Kanyakumari, Padmanabhapuram Palace, the temples and the beaches, by the day or for a full itinerary.

How early should a wedding car be booked?

As early as you can in the wedding season. Decorated cars and the premium vehicles go first, and the dates everybody wants are the ones that run out.

How do I book?

Send the dates through the form on this site or on WhatsApp, and we call you back to confirm what is free. Nothing is paid online — the deposit and the rental are settled when you take the vehicle.

Need a car for your dates?

Tell us when and what you need. We will confirm what is free and what it costs.

+ + diff --git a/lerna.json b/lerna.json deleted file mode 100644 index 8ea483cdb84..00000000000 --- a/lerna.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "lerna": "2.6.0", - "npmClient": "yarn", - "useWorkspaces": true, - "version": "independent", - "changelog": { - "repo": "facebook/create-react-app", - "labels": { - "tag: new feature": ":rocket: New Feature", - "tag: breaking change": ":boom: Breaking Change", - "tag: bug fix": ":bug: Bug Fix", - "tag: enhancement": ":nail_care: Enhancement", - "tag: documentation": ":memo: Documentation", - "tag: internal": ":house: Internal", - "tag: underlying tools": ":hammer: Underlying Tools" - }, - "cacheDir": ".changelog" - } -} diff --git a/marketing/README.md b/marketing/README.md new file mode 100644 index 00000000000..0aef1059470 --- /dev/null +++ b/marketing/README.md @@ -0,0 +1,54 @@ +# marketing + +Artwork for places that are not the website: a Google Business Profile post, +WhatsApp status, a printed card. Nothing in here is served — Apache returns 403 +for `/marketing/` and robots.txt says so too, the same treatment +`assets-original/` gets, because Hostinger deploys the whole repository into the +web root and only the site itself belongs there. + +Each poster is an HTML page rendered to PNG by a script beside it. That is +deliberate: the logo, the photograph, the brand colours and Poppins are already +in this repository, so a poster built from them cannot drift away from the site, +and changing a price or a claim is a text edit and one command rather than a +round trip through a design tool. + +## google-post + +The card for a Google Business Profile post. + +``` +node marketing/google-post/make-post.mjs +``` + +Writes two JPEGs next to `post.html`: + +| File | Size | For | +| --- | --- | --- | +| `nitesha-google-post-1200x900.jpg` | 1200×900 | Google Business Profile post (the size Google asks for) | +| `nitesha-google-post-1080x1080.jpg` | 1080×1080 | WhatsApp status, Instagram, anywhere square | + +Both come off the same page, so the wording is written once. + +### Posting it + +Google Business Profile → **Add update** → **Add photos** → choose the 1200×900 +file → write the text → **Add a button** → *Call now* or *Book*. A post shows +for a week or so before it drops down the profile, so this is worth re-posting +rather than uploading once. + +The image carries the phone number because a post is often seen as a thumbnail +in search, where the buttons are not: somebody who only ever sees the picture +should still have a number to ring. + +### Changing the words + +Everything is in `post.html` — the headline, the three numbers, the line at the +foot. The numbers are the ones the website already claims (1000+ customers, 20+ +vehicles, cleaned before every pickup); if one of those changes on the site, +change it here as well or the two will disagree in public. + +The photograph is the site's own banner image. If there is ever a good +photograph of the actual fleet — a car on a real road in Kanyakumari, a set of +keys being handed over — use that instead. Google's own advice is that real +photographs of the business outperform stock artwork, and it is also simply more +honest about what turns up when someone books. diff --git a/marketing/google-post/make-post.mjs b/marketing/google-post/make-post.mjs new file mode 100644 index 00000000000..d6e1953f72f --- /dev/null +++ b/marketing/google-post/make-post.mjs @@ -0,0 +1,71 @@ +/** + * Renders post.html to the two sizes a Google Business post needs. + * + * Run from the repository root: + * + * node marketing/google-post/make-post.mjs + * + * 1200x900 is what Google asks for on a profile post; 1080x1080 is the same + * card squared off, for WhatsApp status and Instagram, so one edit to the + * wording changes both. + * + * deviceScaleFactor stays at 1 on purpose: Google resizes anything larger and + * a 2400px file is only a slower upload on a phone connection. + */ + +import { createRequire } from 'node:module'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + +// Playwright is not a dependency of this site -- it is installed once on the +// machine that renders the poster. Taken from wherever it is, rather than +// from one particular path, so the suite is not tied to one person's laptop. +const require = createRequire(import.meta.url); +const { chromium } = (() => { + for (const at of [process.env.PLAYWRIGHT_PATH, 'playwright', '/opt/node22/lib/node_modules/playwright']) { + try { + if (at) return require(at); + } catch { + // Try the next one. + } + } + throw new Error('Playwright not found. npm i -g playwright, or set PLAYWRIGHT_PATH.'); +})(); + +const here = dirname(fileURLToPath(import.meta.url)); + +const SIZES = [ + { shape: 'post', width: 1200, height: 900, file: 'nitesha-google-post-1200x900.jpg' }, + { shape: 'square', width: 1080, height: 1080, file: 'nitesha-google-post-1080x1080.jpg' }, +]; + +const browser = await chromium.launch({ + executablePath: '/opt/pw-browsers/chromium-1194/chrome-linux/chrome', +}); + +for (const size of SIZES) { + const page = await browser.newPage({ + viewport: { width: size.width, height: size.height }, + deviceScaleFactor: 1, + }); + + await page.goto(`file://${join(here, 'post.html')}`); + await page.evaluate((shape) => { + document.body.dataset.shape = shape; + }, size.shape); + + // The webfonts and the photograph are both file:// fetches; without this the + // first screenshot catches the fallback face. + await page.evaluate(() => document.fonts.ready); + await page.waitForLoadState('networkidle'); + + // JPEG, not PNG. Most of this card is a photograph, which PNG stores + // losslessly at about a megabyte; at quality 92 the same picture is a fifth + // of that with nothing visible to tell them apart, and the difference is + // the whole wait when the post is uploaded from a phone. + await page.screenshot({ path: join(here, size.file), type: 'jpeg', quality: 92 }); + console.log(`${size.file} ${size.width}x${size.height}`); + await page.close(); +} + +await browser.close(); diff --git a/marketing/google-post/nitesha-google-post-1080x1080.jpg b/marketing/google-post/nitesha-google-post-1080x1080.jpg new file mode 100644 index 00000000000..2617dbea2de Binary files /dev/null and b/marketing/google-post/nitesha-google-post-1080x1080.jpg differ diff --git a/marketing/google-post/nitesha-google-post-1200x900.jpg b/marketing/google-post/nitesha-google-post-1200x900.jpg new file mode 100644 index 00000000000..f1b4c9f0d52 Binary files /dev/null and b/marketing/google-post/nitesha-google-post-1200x900.jpg differ diff --git a/marketing/google-post/post.html b/marketing/google-post/post.html new file mode 100644 index 00000000000..2647cf2f7c3 --- /dev/null +++ b/marketing/google-post/post.html @@ -0,0 +1,361 @@ + + + + + NiteSha Cars & Bikes — Google Business post + + + + + + + +
+
+
+
+ +
+ +

+ Nagercoil + Kanyakumari district +

+
+ +
+

Self drive rentals

+ +

Take the wheel. We bring the car.

+ +

+ Cars and bikes on hire by the day, the week or the month — delivered + to your doorstep. +

+ +
+
+ 1000+ + customers driven since we opened +
+
+ 20+ + cars and bikes to choose from +
+
+ 100% + cleaned and checked before pickup +
+
+
+ +
+

+ + + +91 63749 42976 +

+

+ niteshacars.in + Book online in a minute +

+
+
+ + diff --git a/maruti-brezza-self-drive-car-rental-nagercoil.avif b/maruti-brezza-self-drive-car-rental-nagercoil.avif new file mode 100644 index 00000000000..61092f15d3d Binary files /dev/null and b/maruti-brezza-self-drive-car-rental-nagercoil.avif differ diff --git a/monthly/index.html b/monthly/index.html new file mode 100644 index 00000000000..8dd94aa8844 --- /dev/null +++ b/monthly/index.html @@ -0,0 +1,88 @@ + + + + + + + + + + + + Monthly & Long Term Car Rental in Nagercoil + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Monthly car rental in Nagercoil

Need a car for weeks rather than days? Monthly self drive hire across Kanyakumari district — hatchbacks, sedans, SUVs and 7 seater vehicles.

Hiring by the day adds up quickly. Once you need a vehicle for more than a couple of weeks, a monthly rate is a different and much cheaper arrangement — and you keep the same car throughout rather than starting again every few days.

Who takes a car by the month

Families visiting from abroad

Home for a month or two and need a car the whole time. Works out far cheaper than hiring by the day, and the vehicle stays yours for the stay.

Work postings and projects

Sent to the district for a few months and would rather not ship a car down. Monthly hire covers it without a purchase or a lease.

Between vehicles

Car being repaired, sold, or waiting on delivery. A month on hire fills the gap without a hurried decision.

Extended family visits

Relatives staying for a season, a wedding stretching over weeks, or an elder needing regular hospital trips.

What decides your monthly rate

We quote per booking rather than publishing one figure, because these change what it costs:

  • Which vehicle — a hatchback, a sedan, an SUV or a 7 seater
  • Manual or automatic
  • How long you keep it: the daily rate falls as the hire lengthens
  • Time of year — festival and wedding seasons are busier
  • Where in the district you want it delivered and collected

Tell us the vehicle and the dates and you will have a figure the same day — no obligation.

The things people actually ask

How many kilometres are included?
Monthly hires carry a monthly allowance rather than a daily one, so a long drive one week and none the next evens out. Anything past the allowance is charged at the extra-KM rate, which we tell you before you take the car.
Who services the car during the month?
We do. If a service falls due while you have it, we arrange it and, where we can, give you something else to drive meanwhile so you are not left without.
What if something goes wrong?
Call us. Breakdowns are ours to sort out, not yours — that is the point of hiring rather than buying. For anything major we will get you into another vehicle.
Can I extend if my plans change?
Usually yes, and it is easier if you tell us early — the vehicle may be booked after you. A quick message as soon as you know is enough.
What deposit is needed?
A refundable deposit, higher than a daily hire because the vehicle is with you longer. It comes back when the car does, less any extra-KM charges or damage.
What do I need to bring?
A valid driving licence and government photo ID. Coming from abroad, see the notes on licences for visitors.

Ask about monthly rates

Tell us the vehicle you have in mind and how long you need it.

Visiting from abroad? We can have it waiting at the airport. Based in the district headquarters? Monthly hire in Nagercoil covers how it works there.

Where we deliver

Where we hire across Kanyakumari district

We serve the whole of Kanyakumari district in Tamil Nadu. Tell us where you are and we will sort out pickup.

Somewhere not listed? Ask us — if it is in the district, we can usually help. See every town we deliver to.

+ + diff --git a/monthly/nagercoil/index.html b/monthly/nagercoil/index.html new file mode 100644 index 00000000000..abeacfefc0a --- /dev/null +++ b/monthly/nagercoil/index.html @@ -0,0 +1,90 @@ + + + + + + + + + + + + Monthly Car Rental in Nagercoil — Long Term Self Drive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Monthly car rental in Nagercoil

Hiring by the month is a different transaction from hiring for a weekend, and Nagercoil is where nearly all of it happens — it is the district headquarters, which is where the postings, the offices and the long stays are.

Who takes a car for a month here

Three kinds of people, mostly. Somebody posted to Nagercoil for a few months who does not want to buy a car for it. Somebody home from the Gulf or Singapore for six or eight weeks, which is too long to keep paying a daily rate and too short to buy anything. And somebody between vehicles — a car being rebuilt after an accident, or a delivery date that has slipped.

How the rate works over a month

The daily figure falls as the hire gets longer, so a month is nothing like thirty times a day. There is no down payment and no loan, and the refundable deposit is the only lump sum involved. What you agree at the start is what you pay at the end: the kilometre allowance and the extra-kilometre rate are fixed in writing before you take the car, and a monthly hire gets an allowance sized for a month rather than a daily one multiplied out.

Servicing and what happens if something goes wrong

Over a month a car needs looking after, and that is ours to do rather than yours. If something needs attention we sort it out, and if a vehicle has to go in for work we talk to you about a replacement rather than leaving you without one. This is the part that makes a long hire different from a long series of short ones.

Questions about monthly car rental in Nagercoil

What does it cost to rent a car for a month in Nagercoil?
It depends on the vehicle, and the daily figure is lower than a weekly one, which is lower than a daily one. Tell us the car and the length of the hire and we will give you the monthly figure and the kilometre allowance together, because one without the other does not tell you what you are paying.
Is there a down payment for a monthly rental?
No. There is a refundable security deposit, which comes back when the vehicle does, less anything owing for extra kilometres or damage. There is no loan, no finance and no down payment — that is the main difference from buying.
What happens if the car needs servicing during the month?
We handle it. Over a long hire the vehicle will need attention at some point, and arranging that is our job. If it has to go in for work, we talk to you about a replacement rather than leaving you to manage without.

How it works in Nagercoil

  • The daily rate falls the longer you keep the vehicle
  • No down payment and no loan — a refundable deposit is the only lump sum
  • A kilometre allowance sized for a month, agreed in writing
  • Servicing and upkeep are ours, not yours
Check availability in Nagercoil

or call +91 63749 42976

+ + diff --git a/mountain-road-at-sunrise-self-drive-car-rental-1280w.webp b/mountain-road-at-sunrise-self-drive-car-rental-1280w.webp new file mode 100644 index 00000000000..a4a9461c482 Binary files /dev/null and b/mountain-road-at-sunrise-self-drive-car-rental-1280w.webp differ diff --git a/mountain-road-at-sunrise-self-drive-car-rental-800w.webp b/mountain-road-at-sunrise-self-drive-car-rental-800w.webp new file mode 100644 index 00000000000..c62b62a3acb Binary files /dev/null and b/mountain-road-at-sunrise-self-drive-car-rental-800w.webp differ diff --git a/mountain-road-at-sunrise-self-drive-car-rental.webp b/mountain-road-at-sunrise-self-drive-car-rental.webp new file mode 100644 index 00000000000..f74871a6c29 Binary files /dev/null and b/mountain-road-at-sunrise-self-drive-car-rental.webp differ diff --git a/my-app/.gitignore b/my-app/.gitignore new file mode 100644 index 00000000000..5b8139411ba --- /dev/null +++ b/my-app/.gitignore @@ -0,0 +1,30 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# Written by scripts/fetch-content.mjs before every build: the admin panel's +# copy when it answers, the committed defaults when it does not. Generated, so +# not tracked -- defaults.json is the version under review. +src/content/live.json +src/data/brand.json diff --git a/my-app/.oxlintrc.json b/my-app/.oxlintrc.json new file mode 100644 index 00000000000..6fa991dad24 --- /dev/null +++ b/my-app/.oxlintrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["react", "typescript", "oxc"], + "rules": { + "react/rules-of-hooks": "error", + "react/only-export-components": ["warn", { "allowConstantExport": true }] + } +} diff --git a/my-app/README.md b/my-app/README.md new file mode 100644 index 00000000000..919fb032284 --- /dev/null +++ b/my-app/README.md @@ -0,0 +1,117 @@ +# my-app + +A React single-page app built with Vite, TypeScript, and Tailwind CSS. + +## Requirements + +- Node.js 20.19+ / 22.12+ + +## Getting started + +```bash +npm install +npm run dev +``` + +The dev server prints a local URL (http://localhost:5173 by default). + +## Scripts + +| Command | Description | +| ----------------- | ------------------------------------------------ | +| `npm run dev` | Start the dev server with hot module replacement | +| `npm run build` | Type-check and build to `dist/` | +| `npm run preview` | Serve the production build locally | +| `npm run lint` | Lint with oxlint | + +## Layout + +``` +my-app/ +├── index.html # HTML entry point +├── vite.config.ts # Vite config (React + Tailwind plugins) +├── public/ # Static files copied as-is +└── src/ + ├── main.tsx # App bootstrap + ├── App.tsx # Root component + └── index.css # Tailwind entry +``` + +## Deployment + +Deployed to GitHub Pages at by +`.github/workflows/deploy-my-app.yml`, which builds this directory and +publishes `dist/`. + +It runs on pushes to `main` that touch `my-app/`, and can also be started +by hand from the Actions tab (Run workflow). + +`public/CNAME` holds the custom domain. Vite copies `public/` into `dist/` +verbatim, so the file lands at the site root where Pages expects it. +Removing it reverts the site to the default `*.github.io` address. + +## Editing the fleet + +`src/data/cars.ts` holds the vehicles shown on the site. They are placeholders. + +The live fleet lives in the admin database, but `api/vehicles.php` calls +`api_guard('vehicle.view')`, so it cannot be read without signing in — a public +site has no way to fetch it. Edit the file to match the real fleet; pushing to +`main` redeploys automatically. + +Fields mirror the `vehicles` and `vehicle_rates` tables, so the two stay +comparable. + +## Enquiry form + +The form posts to `https://admin.niteshacars.in/api/enquiry-submit.php`. +That endpoint needs no sign-in and already allowlists this site's origin for +CORS via the `public_site_origin` config key. + +`vehicle_id` is deliberately not sent. The endpoint validates it against the +`vehicles` table, and the ids in `cars.ts` are placeholders that do not exist +there, so sending one would be rejected. The chosen car is sent as free text in +`requirements` instead. Once `cars.ts` carries real database ids, the id can be +sent and enquiries will link to the vehicle record. + +A honeypot field named `website` is included and kept visually hidden, matching +what the endpoint expects. + +## Where the fleet comes from + +The site fetches `https://admin.niteshacars.in/api/public-vehicles.php`, +which returns only vehicles whose status is `Available`, and only public-safe +columns. Adding a car in the admin panel puts it on the site; setting one to +Maintenance takes it off. No code change, no deploy. + +`src/data/cars.ts` is the fallback used when that request fails — offline, a +CORS rejection, or the endpoint not uploaded yet. It is empty, so the site +degrades to its "ask us what's available" state rather than showing an error. + +The endpoint lives in this repo at +`admin/api/public-vehicles.php` and reaches the +server through the admin deploy workflow. + +Note the two paths are not the same shape. That repo folder maps onto the +subdomain's document root, so `admin/api/x.php` in git is served at +`https://admin.niteshacars.in/api/x.php` -- with no `admin` segment. Assuming +otherwise is what kept the fleet empty: a 404 there is swallowed by the +fallback below rather than reported. + +## SEO + +`src/data/seo.json` is the single source of per-route titles and descriptions. +It feeds two things: + +- `scripts/prerender-seo.mjs`, which runs after each build and writes a + directory per route (`dist/about/index.html` and so on) with that route's + title, description, canonical and `og:` tags baked into the HTML. This is + what crawlers and link previews read — WhatsApp, Facebook and X do not run + JavaScript, so tags set at runtime never reach them. +- `src/lib/useSeo.ts`, which updates the same tags on client-side navigation, + since the app never re-requests HTML after boot. + +The script also writes `sitemap.xml`. `public/robots.txt` points at it. + +`index.html` carries `AutoRental` structured data — the business name, phone +and email — which is what local search results are built from. diff --git a/my-app/index.html b/my-app/index.html new file mode 100644 index 00000000000..6e2676150e1 --- /dev/null +++ b/my-app/index.html @@ -0,0 +1,114 @@ + + + + + + + + + + + + Self-Drive Car Rental in Kanyakumari — NiteSha Cars & Bikes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/my-app/package-lock.json b/my-app/package-lock.json new file mode 100644 index 00000000000..64faa622443 --- /dev/null +++ b/my-app/package-lock.json @@ -0,0 +1,2000 @@ +{ + "name": "my-app", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "my-app", + "version": "0.0.0", + "dependencies": { + "react": "^19.2.8", + "react-dom": "^19.2.8", + "react-router-dom": "^7.18.3" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.3.3", + "@types/node": "^24.13.3", + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.7", + "@vitejs/plugin-react": "^6.1.1", + "oxlint": "^1.81.0", + "tailwindcss": "^4.3.3", + "typescript": "~6.0.2", + "vite": "^8.3.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.149.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.149.0.tgz", + "integrity": "sha512-Efcc+iF0j3Bf67YjEqIqWXbX5XddXoK/Mw4K1/JuXwRCZ8N16VR7iT23nlCc9XrveFVh/E5Rqs2StT0V8v9LdA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@oxlint/binding-android-arm-eabi": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.82.0.tgz", + "integrity": "sha512-a3LB+C5Dsj5b/qtmG/mv5WrzuiXEpg1KF5nXWcEvaoN5TYAqkIvxPOwTPp3Jy/FoGpRo8zsTFhMElMXfeoOEzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-android-arm64": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.82.0.tgz", + "integrity": "sha512-OBlhRgNqFblGpGenno/aqOfJLOkQ2B8Ig3iDAalfn0H8hJGZKXPeexCRTDm6uwv6YUjSA9Xnwt1y/Bgj5ZH8uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-arm64": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.82.0.tgz", + "integrity": "sha512-dsopxqtY5ZdyT9uLHyGt1SyiLop6hi7hWI3PKpePodkRQOkLaCm+OE4fR9CAz9qdfjiFO8531tX/QDyP/psjFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-x64": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.82.0.tgz", + "integrity": "sha512-94Lu0SgTClKColU66g1VDuigV3HkcbkJBnTtZjGYfE8UPugaWDgKrm2icjC6HJVUYler2OXaHP/X0TBy8+CowQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-freebsd-x64": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.82.0.tgz", + "integrity": "sha512-hne/V06ewhh1i0w8+l7GDNROAGCGPmyFuOwiP7YTRu0JycyStJ4785dmF8xU5p0uUwt2emvIF9vc7Xjis+cJ0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-gnueabihf": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.82.0.tgz", + "integrity": "sha512-aWY2xtbZf1LneW9Qsv/n2Sp8gOu74JrlQzEtj4coHX2SHFrCfhmAumaU+sI/A5nr+yoTRTSmI/pL2s6ADlNSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-musleabihf": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.82.0.tgz", + "integrity": "sha512-Fe+TtXCXMh/5f7kWlZ2VAwsMumZWtraFlKVk1NJlL52/beGwfDE7ov+/8gVirHzWokzGu7X65hSPq0ucPDskWQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-gnu": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.82.0.tgz", + "integrity": "sha512-6azCZ6OJudlvipNttXCCQcyeFfcJ/NvUZdSN1z8elo73kCHtyQC7WTiUcSjWYvJ1jaq9KDUyMAoAS/vNzhBomA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-musl": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.82.0.tgz", + "integrity": "sha512-PLEaSD8IAIIlwW4dwOd9YaxuxeOpwiXL4J24rcnE4iNtyM5j9Q9/3+gti08oXpx0u2ygNjRDx9xjWWpQonuJEw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-ppc64-gnu": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.82.0.tgz", + "integrity": "sha512-D94em/BwknNTn4vqxjHh5wb2oL566eFhArabqKIr0cNZMHOJuiraFp1A8tXpH05bbE5tqwEfLXTI0MWEGtn3Dw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-gnu": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.82.0.tgz", + "integrity": "sha512-MOprxBaoYU2D4VgxXCl3ghydThWtx7Um1lL51kGYNeQ5Al7WzsH7/tqGdNtbLrIWnjq3bsm13+nz/gRIxjrOXw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-musl": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.82.0.tgz", + "integrity": "sha512-5h55QsfJ/luDXZzC20k6SNOY1Az+dCP9WvntKtcUWh2JhckAdwApY2ZusaBTwLENnReXU+A2fJtSrYvZJNKNPg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-s390x-gnu": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.82.0.tgz", + "integrity": "sha512-IE8NJNLlHr0CaXyGJPGVn0eTkUyoj1I2UfA8x7I4PSOYKsQ/6btVC7Pywrj5onk0cMH25r6Z38SoN3AvE5Zuog==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-gnu": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.82.0.tgz", + "integrity": "sha512-XUUUxaBo9XKl+J1B9EmP1cTGQPddzeURvoGkfwh/94PGnbW+hBprDljneoI2M1jzC1bzrIV3ihc7iM9UXl8+tg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-musl": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.82.0.tgz", + "integrity": "sha512-SWLSFulX9TDuH6yvbPYp4+VNn6jkkIvvI+KiujDM5rWBRHEfkesCC/pCneIIUr6ovkxZ5fRtpi2v5Cz5FrMJZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-openharmony-arm64": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.82.0.tgz", + "integrity": "sha512-BQy35f6ZUdNr9a6c7B7orxQTcLjByGT2z3WAgmRovpRwmPYAaJ+NTplmMzhdjdJ4qSchfMNZy/Ukg+qRg6zseQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-arm64-msvc": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.82.0.tgz", + "integrity": "sha512-V4QhSTg5gctZue8RJjsGi7NpQPThr/p1/HfmiMC5kfe1KFEup9SQRVub4A6kijQjdHfxj7bLL1KO3QO7/5bwMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-ia32-msvc": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.82.0.tgz", + "integrity": "sha512-TUSCLaKB2yktpFAJ/r3HAUYsaV/3DT7JS4iNKyoh3a9YNwD0UG7Ezh4D8m23654vQcU6P/RQrCAjRPKe4peP/A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-x64-msvc": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.82.0.tgz", + "integrity": "sha512-VTVoRIWJTb+wvUX8EYoPArfFH02whuR10goFXE/LHRRX33ajRrFgqbcONXZMiF4C5rnattfkm87HqYn8jb8hmQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.8.tgz", + "integrity": "sha512-tN5aztYkKCte4i5SIrrz5yK/HMjEuCqCSCJa418jOV8tZ1cBY3YF2otxB1ktPxzsLA1BeTqwapK0bfjxNvHJVw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.8.tgz", + "integrity": "sha512-dIYTWl9XprMUiQFoc55KUyk/oS8SKYH3zFl0LTR7RT0Xj4hgSVyuJcroH8JUu8RcpF8fTB6E0aOwCkZoYPcDSQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.8.tgz", + "integrity": "sha512-PCSDQGXD2IyTEFrcgPyBM8jJuGmrbCMuoIOXdbEGVemruKACXoLQJrb+A45Z0L5t1RQkdfJprAYPkikbh7dzdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.8.tgz", + "integrity": "sha512-Uk7lRsGhPFHVX/sAUC6D5H9Ol30dFHd6iquokll2th3LpdJ3F5CzQB+7DHn0Ri2mG+U7k2zXiPHDrwZenXhwSA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.8.tgz", + "integrity": "sha512-DjszaTEVogPqA5bYzsEeqDCQxbcp2fexQwKcRspYji2yzR68fCf+e4fx6kBSRDwX5/brZaHw/hWS9+A/+/w9sQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.8.tgz", + "integrity": "sha512-zmwa7FTmdzB6aaEEuuls18H6Ap5JmJPSoPTuXixeJZV6tG40SyLkApQtz1g8ptZtiEKqj9OM0oNLPh1AgvE31Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.8.tgz", + "integrity": "sha512-KdYQDPHwJVnbFwdTGMgxsI9SqblBlz6STGM+w1We/d5B8OWWidYH0MwkU/uA1wM5fIpO2MkOVxXrNzzuZhw9ew==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.8.tgz", + "integrity": "sha512-jFJTifHnNPY+yzOoNZQfSIysrVyXzEQPhPnOUjmD1bcQGHH6s7c8cViKWar8YplQImE5N9JRqMCLrM2CdxOrZA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.8.tgz", + "integrity": "sha512-FhiOziBDWPBjbcmRzfLyIJnaP7AVMFXT7YCXPjXxj7wKU3vx24RjrCNN/zjvVa+N2vVoHJwCoUBvsrN/DG3zIA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.8.tgz", + "integrity": "sha512-WnHfADMzOV2Y55wlx1hzzQnar/wDt/VdvWSD99r18Mz9ylNieIGOkRx3UV21h7m/eJvjySYJkO26VvGNFkwsIQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.8.tgz", + "integrity": "sha512-H9tRr5ibfXFVLxbPOseVewewFpl28zcEdjRDt2FTUZU7odxP0gEv1ki4/kGmcGOh78oRwZuuQllGLZ9zTJp84g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.8.tgz", + "integrity": "sha512-UefiqfM3D6IVNlZ8tSGs9+Ejjud2T+oxO0IHADU45Y+lyEjD2dVFyZHbkfX0LUb5Zugo/oIv1eCO/KVYhgYJYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.8.tgz", + "integrity": "sha512-637Ke4kWSy6rp9cxQ9gMOXlxPgIw/c1beASV4M//3+9I4uwBVOOl74G+e3zyU3u19U7RkRl/HuewixZ/Z6+Rjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.8.tgz", + "integrity": "sha512-xWBkPOF1Q9k/Gv1nQXnVdLxKu74jXppuOM4Z3mnypVUJJJwLsMl7hNJGRAUJoG8A5MgOI1ACKM+wBFxSJzKy4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.8.tgz", + "integrity": "sha512-uz2ZvfgXbxqNwijjjbxrnvALwpyODDcgc1T1N8N3rf/DXKQmaFwmB4LX4yyjggpwN2obdQLb2rgirX5ffCWYng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.24.1", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.3" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz", + "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "tailwindcss": "4.3.3" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@types/node": { + "version": "24.13.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.4.tgz", + "integrity": "sha512-YJ7EqCstVTzIr0fMr7qul/977en+pQHrfmuKIo6Zr9i75Be21dr3MovcfvGtyvi2HAUrRerWps5sMO9I7WaxDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", + "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.3.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz", + "integrity": "sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "oxc-transform-react": "^0.145.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "oxc-transform-react": { + "optional": true + } + } + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/nanoid": { + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/oxlint": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.82.0.tgz", + "integrity": "sha512-+iFM1BGw1ntYJt3QngbJmjbrGxPaKMUADOXOijpWGnYcBPq8YZnQftSS1C+pVcDYy9YxqDVJKQqQkTazTQMboQ==", + "dev": true, + "license": "MIT", + "bin": { + "oxlint": "bin/oxlint" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/oxc-project" + }, + "optionalDependencies": { + "@oxlint/binding-android-arm-eabi": "1.82.0", + "@oxlint/binding-android-arm64": "1.82.0", + "@oxlint/binding-darwin-arm64": "1.82.0", + "@oxlint/binding-darwin-x64": "1.82.0", + "@oxlint/binding-freebsd-x64": "1.82.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.82.0", + "@oxlint/binding-linux-arm-musleabihf": "1.82.0", + "@oxlint/binding-linux-arm64-gnu": "1.82.0", + "@oxlint/binding-linux-arm64-musl": "1.82.0", + "@oxlint/binding-linux-ppc64-gnu": "1.82.0", + "@oxlint/binding-linux-riscv64-gnu": "1.82.0", + "@oxlint/binding-linux-riscv64-musl": "1.82.0", + "@oxlint/binding-linux-s390x-gnu": "1.82.0", + "@oxlint/binding-linux-x64-gnu": "1.82.0", + "@oxlint/binding-linux-x64-musl": "1.82.0", + "@oxlint/binding-openharmony-arm64": "1.82.0", + "@oxlint/binding-win32-arm64-msvc": "1.82.0", + "@oxlint/binding-win32-ia32-msvc": "1.82.0", + "@oxlint/binding-win32-x64-msvc": "1.82.0" + }, + "peerDependencies": { + "oxlint-tsgolint": ">=7.0.2001", + "vite-plus": "*" + }, + "peerDependenciesMeta": { + "oxlint-tsgolint": { + "optional": true + }, + "vite-plus": { + "optional": true + } + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz", + "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.28.0" + }, + "peerDependencies": { + "react": "^19.3.0" + } + }, + "node_modules/react-router": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.3.tgz", + "integrity": "sha512-gyXgtdr5uACJ5b1Q4udzjVV+tb/rlHIMJKuJ0e89R4Kzgz47z/rgP0dIKxktqIEUhDHluGTPJJH/wRha7CyqsA==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.3.tgz", + "integrity": "sha512-ytVbyBBM7vMfRCam25r0WMhSVSom909A8p+8m0/f1w853dz/xfFu6etAT2SEbVoSnI+ZoPRDqIsQXVT89gp7kg==", + "license": "MIT", + "dependencies": { + "react-router": "7.18.3" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/rolldown": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.8.tgz", + "integrity": "sha512-Z67nTmhZe7anqnM/EjI392w5i/ANUinjip7QYsOyN37oayduxt3ksdX0hf5OOamkAd53BiIHfbfSzfUmzKFQqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.149.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.8", + "@rolldown/binding-android-arm64": "1.2.8", + "@rolldown/binding-darwin-arm64": "1.2.8", + "@rolldown/binding-darwin-x64": "1.2.8", + "@rolldown/binding-freebsd-x64": "1.2.8", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.8", + "@rolldown/binding-linux-arm64-gnu": "1.2.8", + "@rolldown/binding-linux-arm64-musl": "1.2.8", + "@rolldown/binding-linux-ppc64-gnu": "1.2.8", + "@rolldown/binding-linux-s390x-gnu": "1.2.8", + "@rolldown/binding-linux-x64-gnu": "1.2.8", + "@rolldown/binding-linux-x64-musl": "1.2.8", + "@rolldown/binding-openharmony-arm64": "1.2.8", + "@rolldown/binding-win32-arm64-msvc": "1.2.8", + "@rolldown/binding-win32-x64-msvc": "1.2.8" + } + }, + "node_modules/scheduler": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", + "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", + "license": "MIT" + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.0.tgz", + "integrity": "sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.28", + "rolldown": "~1.2.6", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.7.1", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} diff --git a/my-app/package.json b/my-app/package.json new file mode 100644 index 00000000000..5cc2e4da681 --- /dev/null +++ b/my-app/package.json @@ -0,0 +1,29 @@ +{ + "name": "my-app", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "node scripts/photo-manifest.mjs && node scripts/fetch-content.mjs && node scripts/fetch-fleet.mjs && node scripts/fetch-places.mjs && vite", + "build": "node scripts/photo-manifest.mjs && node scripts/fetch-content.mjs && node scripts/fetch-fleet.mjs && node scripts/fetch-places.mjs && tsc -b && vite build && vite build --ssr src/entry-server.tsx --outDir dist-ssr && node scripts/copy-admin-panel.mjs && node scripts/prerender-seo.mjs", + "lint": "oxlint", + "preview": "vite preview", + "seo": "node scripts/seo-audit.mjs" + }, + "dependencies": { + "react": "^19.2.8", + "react-dom": "^19.2.8", + "react-router-dom": "^7.18.3" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.3.3", + "@types/node": "^24.13.3", + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.7", + "@vitejs/plugin-react": "^6.1.1", + "oxlint": "^1.81.0", + "tailwindcss": "^4.3.3", + "typescript": "~6.0.2", + "vite": "^8.3.0" + } +} diff --git a/my-app/public/.htaccess b/my-app/public/.htaccess new file mode 100644 index 00000000000..02c2f192fab --- /dev/null +++ b/my-app/public/.htaccess @@ -0,0 +1,199 @@ +# Apache configuration for niteshacars.in on Hostinger. +# +# This file only matters once the domain points at Hostinger rather than +# GitHub Pages. Pages ignores it; Apache reads it on every request. +# +# It has three jobs: keep everyone on HTTPS, keep /admin out of the way of the +# website's routing, and serve a real 404 for a URL that does not exist. + +# ----------------------------------------------------------------- routing -- + + RewriteEngine On + + # HTTPS. Hostinger terminates TLS upstream, so %{HTTPS} is not reliable here + # -- the X-Forwarded-Proto header is what actually says how the visitor + # arrived. Both are checked, because a setup where only %{HTTPS} is set would + # otherwise redirect forever. + RewriteCond %{HTTP:X-Forwarded-Proto} =http [OR] + RewriteCond %{HTTPS} !=on + RewriteCond %{HTTP:X-Forwarded-Proto} !=https + RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L] + + # /admin is the PHP panel. Hand it straight to PHP and stop: without this, + # the website's 404 rule below would swallow every panel URL that is not a + # file on disk, and the panel would answer every page with the website's + # "page not found". + RewriteCond %{REQUEST_URI} ^/admin(/|$) [NC] + RewriteRule ^ - [L] + + # ------------------------------------------------- where the pictures live -- + # + # Every photograph on this site is stored above the document root, so Apache + # cannot serve it and a small PHP reader in the panel has to. That used to + # show in the address: + # + # /admin/photos/maruti-swift-self-drive-nagercoil-9f3a1c4b7d2e5a60.jpg + # + # The words were right and the folder was wrong. Two things follow from + # "admin" being in the URL of every picture on a site that sells car hire: + # + # 1. It is a dead word in the one part of an image's address Google reads + # besides the file name. /car-photos/ is not. + # + # 2. It is one robots.txt edit from disaster. Disallowing /admin/ is the + # normal, obvious thing to do -- any SEO tool will suggest it -- and + # doing it would make every photograph on the site uncrawlable at a + # stroke. robots.txt carries three Allow lines to prevent exactly that, + # and a rule that survives only because nobody tidies it is not a rule. + # + # Served from the site root instead, which is where a visitor would assume + # they live anyway. The /admin/ addresses still work -- they are what is + # stored in pages already indexed -- so this adds an address, it does not + # move one. + # The static pictures were renamed to say what they show rather than where + # they sit on the page. Google Images may hold the old addresses, and a + # renamed file is a 404 to anything that already knew the old one -- so the + # old names redirect rather than simply stopping. 301, because the move is + # permanent and that is what passes the old address's standing to the new. + RewriteRule ^hero-car\.webp$ /self-drive-car-rental-nitesha-cars-and-bikes.webp [R=301,L] + RewriteRule ^open-road\.webp$ /mountain-road-at-sunrise-self-drive-car-rental.webp [R=301,L] + RewriteRule ^car-brezza\.avif$ /maruti-brezza-self-drive-car-rental-nagercoil.avif [R=301,L] + RewriteRule ^photos/car-brezza\.avif$ /photos/car-brezza--maruti-brezza-self-drive-rental-nagercoil.avif [R=301,L] + + RewriteRule ^car-photos/([A-Za-z0-9._-]+)$ /admin/photo.php?f=$1 [L] + RewriteRule ^site-images/([A-Za-z0-9._-]+)$ /admin/brand.php?f=$1 [L] + RewriteRule ^place-photos/([A-Za-z0-9._-]+)$ /admin/place-photo.php?f=$1 [L] + + # The site's pages are real directories with real index.html files -- the + # build writes one per route so each carries its own title and description. + # Apache serves those itself, so there is nothing to rewrite for a URL that + # exists. Anything left over genuinely does not exist, and gets 404.html + # with a 404 status rather than a copy of the home page with a 200: a soft + # 404 tells Google the page is real and gets the dud URL indexed. + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule ^ /404.html [L] + + +ErrorDocument 404 /404.html + +# ------------------------------------------------------------- compression -- +# +# Every page is real HTML now rather than an empty
, which is +# what made the site readable before its JavaScript arrives -- and it made the +# home page 74 KB instead of 3 KB. Compressed it is about 12 KB, so this is +# most of that cost handed back, on the one file that has to arrive before +# anything at all can be drawn. +# +# Brotli first where the host has it: it beats gzip by roughly 15% on markup +# and every browser this site sees supports it. gzip is the fallback, and one +# of the two is on every Apache install worth deploying to. +# +# Images and fonts are left alone deliberately. WebP, AVIF, JPEG and WOFF2 are +# already compressed; running them through again spends CPU on every request to +# make the file very slightly larger. + + AddOutputFilterByType BROTLI_COMPRESS text/html text/plain text/xml text/css + AddOutputFilterByType BROTLI_COMPRESS application/javascript application/json + AddOutputFilterByType BROTLI_COMPRESS image/svg+xml application/xml + + + + AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css + AddOutputFilterByType DEFLATE application/javascript application/json + AddOutputFilterByType DEFLATE image/svg+xml application/xml + # Proxies must not hand a compressed copy to a client that cannot read it. + + Header append Vary Accept-Encoding + + + +# ----------------------------------------------------------------- caching -- + + ExpiresActive On + + # Build assets carry a content hash in the filename, so a changed file is a + # changed URL and these can be cached hard. + + ExpiresDefault "access plus 1 year" + Header set Cache-Control "public, max-age=31536000, immutable" + + + # Photographs keep their names across edits, so a year would mean a visitor + # who has seen the old one never gets the new one. A week is long enough to + # help and short enough to fix a mistake. + + ExpiresDefault "access plus 1 week" + + + # HTML is the index of everything else, so it is always revalidated. Cache + # it and a deploy would not reach anyone who had visited before. + + ExpiresDefault "access plus 0 seconds" + Header set Cache-Control "no-cache, must-revalidate" + + + +# ---------------------------------------------------------------- security -- + + # Stops a browser from second-guessing a file's declared type, which is how + # an uploaded image gets treated as a script. + Header set X-Content-Type-Options "nosniff" + Header set Referrer-Policy "strict-origin-when-cross-origin" + # Nothing here needs a camera, a microphone or a location. + Header set Permissions-Policy "camera=(), microphone=(), geolocation=()" + + +# Dotfiles are configuration, not content. .htaccess itself is already +# protected by Apache, but this covers anything else that lands here. + + Require all denied + + +# FilesMatch above matches file NAMES, which is not enough for a directory: +# a request for /.git/config has a filename of "config" and sails straight +# past it. This blocks the paths instead. +# +# A served .git is not a tidiness problem -- standard tooling reconstructs the +# entire repository from it, and this repository's history contains a database +# password from before it was moved out. +# +# This is no longer a precaution. Hostinger's Git deployment clones the whole +# repository into the document root, so .git is there on every deploy by +# design, and cannot be removed without giving up automatic deploys. These +# rules are what stands between it and the internet, which is also why the +# password still needs rotating in hPanel rather than being considered handled. + + RewriteEngine On + RewriteRule (^|/)\.(git|svn|hg|env|ssh)(/|$) - [F,L] + + # .github is not caught above: the pattern needs a slash or the end of the + # path straight after "git", and this has "hub" there instead. + RewriteRule (^|/)\.github(/|$) - [F,L] + + # The rest of the repository, which now sits in the web root beside the + # site. FilesMatch cannot express these because it matches one filename at a + # time, and what needs blocking here is whole trees. + RewriteRule ^(my-app|scripts|assets-original|marketing)(/|$) - [F,L] + + +# Source and build files are not content either. None of these should ever +# reach a web root, but naming them costs nothing and a stray upload is +# exactly how they arrive. + + Require all denied + + +# Documentation, which the deploy now puts in the web root: README.md, +# CODE_OF_CONDUCT.md, LICENSE, and the note in photos/ describing how to name a +# car photograph. None of it is secret and none of it is for visitors. + + Require all denied + + + Require all denied + + +# The directory listing is off by default on most hosts, but not all, and an +# open listing of the web root is a map of everything on the server. +Options -Indexes diff --git a/my-app/public/CNAME b/my-app/public/CNAME new file mode 100644 index 00000000000..c53fa10d62b --- /dev/null +++ b/my-app/public/CNAME @@ -0,0 +1 @@ +niteshacars.in diff --git a/my-app/public/apple-touch-icon.png b/my-app/public/apple-touch-icon.png new file mode 100644 index 00000000000..c415d707ce5 Binary files /dev/null and b/my-app/public/apple-touch-icon.png differ diff --git a/my-app/public/favicon.svg b/my-app/public/favicon.svg new file mode 100644 index 00000000000..21ce356d673 --- /dev/null +++ b/my-app/public/favicon.svg @@ -0,0 +1,37 @@ + + + NiteSha Cars & Bikes + + + + + + + + + + + + + + + + + + + + + diff --git a/my-app/public/fleet-check.html b/my-app/public/fleet-check.html new file mode 100644 index 00000000000..4bb67d69a96 --- /dev/null +++ b/my-app/public/fleet-check.html @@ -0,0 +1,484 @@ + + + + + + +Site check + + + + +
+

Site check

+

Asks the website's own questions and reports what this server said.

+
+
+ + + + diff --git a/my-app/public/fonts/poppins-latin-400.woff2 b/my-app/public/fonts/poppins-latin-400.woff2 new file mode 100644 index 00000000000..b69e0091c26 Binary files /dev/null and b/my-app/public/fonts/poppins-latin-400.woff2 differ diff --git a/my-app/public/fonts/poppins-latin-500.woff2 b/my-app/public/fonts/poppins-latin-500.woff2 new file mode 100644 index 00000000000..c6603368bf1 Binary files /dev/null and b/my-app/public/fonts/poppins-latin-500.woff2 differ diff --git a/my-app/public/fonts/poppins-latin-600.woff2 b/my-app/public/fonts/poppins-latin-600.woff2 new file mode 100644 index 00000000000..921e962b91d Binary files /dev/null and b/my-app/public/fonts/poppins-latin-600.woff2 differ diff --git a/my-app/public/fonts/poppins-latin-700.woff2 b/my-app/public/fonts/poppins-latin-700.woff2 new file mode 100644 index 00000000000..bf022fc77e7 Binary files /dev/null and b/my-app/public/fonts/poppins-latin-700.woff2 differ diff --git a/my-app/public/fonts/poppins-latin-ext-400.woff2 b/my-app/public/fonts/poppins-latin-ext-400.woff2 new file mode 100644 index 00000000000..63f4711779c Binary files /dev/null and b/my-app/public/fonts/poppins-latin-ext-400.woff2 differ diff --git a/my-app/public/fonts/poppins-latin-ext-500.woff2 b/my-app/public/fonts/poppins-latin-ext-500.woff2 new file mode 100644 index 00000000000..d59af06e173 Binary files /dev/null and b/my-app/public/fonts/poppins-latin-ext-500.woff2 differ diff --git a/my-app/public/fonts/poppins-latin-ext-600.woff2 b/my-app/public/fonts/poppins-latin-ext-600.woff2 new file mode 100644 index 00000000000..8ec78f544ef Binary files /dev/null and b/my-app/public/fonts/poppins-latin-ext-600.woff2 differ diff --git a/my-app/public/fonts/poppins-latin-ext-700.woff2 b/my-app/public/fonts/poppins-latin-ext-700.woff2 new file mode 100644 index 00000000000..bc847e9d2e8 Binary files /dev/null and b/my-app/public/fonts/poppins-latin-ext-700.woff2 differ diff --git a/my-app/public/fonts/poppins-rupee-400.woff2 b/my-app/public/fonts/poppins-rupee-400.woff2 new file mode 100644 index 00000000000..3f989e54484 Binary files /dev/null and b/my-app/public/fonts/poppins-rupee-400.woff2 differ diff --git a/my-app/public/fonts/poppins-rupee-500.woff2 b/my-app/public/fonts/poppins-rupee-500.woff2 new file mode 100644 index 00000000000..e0c4c5a9472 Binary files /dev/null and b/my-app/public/fonts/poppins-rupee-500.woff2 differ diff --git a/my-app/public/fonts/poppins-rupee-600.woff2 b/my-app/public/fonts/poppins-rupee-600.woff2 new file mode 100644 index 00000000000..a1e92d7c749 Binary files /dev/null and b/my-app/public/fonts/poppins-rupee-600.woff2 differ diff --git a/my-app/public/fonts/poppins-rupee-700.woff2 b/my-app/public/fonts/poppins-rupee-700.woff2 new file mode 100644 index 00000000000..f3199d29415 Binary files /dev/null and b/my-app/public/fonts/poppins-rupee-700.woff2 differ diff --git a/my-app/public/maruti-brezza-self-drive-car-rental-nagercoil.avif b/my-app/public/maruti-brezza-self-drive-car-rental-nagercoil.avif new file mode 100644 index 00000000000..61092f15d3d Binary files /dev/null and b/my-app/public/maruti-brezza-self-drive-car-rental-nagercoil.avif differ diff --git a/my-app/public/mountain-road-at-sunrise-self-drive-car-rental-1280w.webp b/my-app/public/mountain-road-at-sunrise-self-drive-car-rental-1280w.webp new file mode 100644 index 00000000000..a4a9461c482 Binary files /dev/null and b/my-app/public/mountain-road-at-sunrise-self-drive-car-rental-1280w.webp differ diff --git a/my-app/public/mountain-road-at-sunrise-self-drive-car-rental-800w.webp b/my-app/public/mountain-road-at-sunrise-self-drive-car-rental-800w.webp new file mode 100644 index 00000000000..c62b62a3acb Binary files /dev/null and b/my-app/public/mountain-road-at-sunrise-self-drive-car-rental-800w.webp differ diff --git a/my-app/public/mountain-road-at-sunrise-self-drive-car-rental.webp b/my-app/public/mountain-road-at-sunrise-self-drive-car-rental.webp new file mode 100644 index 00000000000..f74871a6c29 Binary files /dev/null and b/my-app/public/mountain-road-at-sunrise-self-drive-car-rental.webp differ diff --git a/my-app/public/nitesha-cars-and-bikes-emblem.webp b/my-app/public/nitesha-cars-and-bikes-emblem.webp new file mode 100644 index 00000000000..72297f596e3 Binary files /dev/null and b/my-app/public/nitesha-cars-and-bikes-emblem.webp differ diff --git a/my-app/public/nitesha-cars-and-bikes-logo-nagercoil-v2-400w.webp b/my-app/public/nitesha-cars-and-bikes-logo-nagercoil-v2-400w.webp new file mode 100644 index 00000000000..f26691f06fa Binary files /dev/null and b/my-app/public/nitesha-cars-and-bikes-logo-nagercoil-v2-400w.webp differ diff --git a/my-app/public/nitesha-cars-and-bikes-logo-nagercoil-v2-800w.webp b/my-app/public/nitesha-cars-and-bikes-logo-nagercoil-v2-800w.webp new file mode 100644 index 00000000000..52f0fbeacf3 Binary files /dev/null and b/my-app/public/nitesha-cars-and-bikes-logo-nagercoil-v2-800w.webp differ diff --git a/my-app/public/nitesha-cars-and-bikes-logo-nagercoil-v2.webp b/my-app/public/nitesha-cars-and-bikes-logo-nagercoil-v2.webp new file mode 100644 index 00000000000..23953052192 Binary files /dev/null and b/my-app/public/nitesha-cars-and-bikes-logo-nagercoil-v2.webp differ diff --git a/my-app/public/photos/README.md b/my-app/public/photos/README.md new file mode 100644 index 00000000000..0efc67ce546 --- /dev/null +++ b/my-app/public/photos/README.md @@ -0,0 +1,92 @@ +# Photographs + +Drop image files in here and the site uses them. Nothing to register. + +The filename (without its extension) is the slot. `cars-hero.jpg` fills the +`cars-hero` slot; rename a file and it moves slots. `.avif`, `.webp`, `.jpg` +and `.png` all work, and a slot with several formats prefers them in that +order — so adding `cars-hero.webp` beside `cars-hero.jpg` is an upgrade, not +a conflict. + +## Words in the file name + +Anything after a **double hyphen** is words for the address, not part of the +slot: + +``` +cars-hero--self-drive-cars-for-rent-nagercoil.webp +└── slot ──┘└──────── words in the URL ─────────┘ +``` + +That file still fills `cars-hero`. The words matter because the file name is +one of the few things Google knows about a picture besides its alt text and +the page around it, and `cars-hero.webp` tells it nothing. + +Describe what is actually in the photograph, and add the place only when the +photograph is of that place. A file called +`self-drive-car-rental-kanyakumari.jpg` showing a stock car on a European +mountain road is a claim the picture does not support, and Google is better at +noticing that than it used to be. Rewording is free — the slot does not move. + +Any slot with no file falls back to the drawn scene, so a half-finished set +of photographs still gives a complete-looking site. + +## Slots the pages look for + +| File name | Where it appears | What it should show | +| ------------------------ | -------------------------- | ----------------------------------------------------- | +| `cars-hero` | Cars page banner | Two or three of your cars together, outdoors | +| `bikes-hero` | Bikes page banner | Scooters and a motorcycle, lined up | +| `wedding-hero` | Wedding cars banner | A decorated car, flowers visible | +| `tourist-hero` | Tourist vehicles banner | Your van or a group vehicle, ideally at a landmark | +| `monthly-hero` | Monthly rental banner | A car parked at a home or office | +| `nri-hero` | NRI page banner | A car at an airport pickup, or with luggage loaded | +| `tariff-hero` | Tariff page banner | Any clean, wide shot of a car | +| `about-hero` | About page banner | You, your team, or the yard | +| `contact-hero` | Contact page banner | Your office front, or a recognisable local landmark | +| `/cars` `/bikes` … | Home page service cards | Same subjects, used as the card headers | +| `coast` | Areas-served panel | Kanyakumari coastline or the lighthouse | + +## Shooting notes + +- **Landscape, and wide.** The banners crop to roughly 3:1 on a desktop, so + anything important near the top or bottom edge gets cut. Leave room. +- **At least 1600px wide.** These stretch the full width of the page; below + that they go soft on a laptop screen and badly soft on a phone. +- **Daylight, and not midday.** Early morning or an hour before sunset. Hard + overhead sun blows out a car's bonnet and fills the windows with glare. +- **Clean cars.** Obvious, and the thing most often skipped. +- **No number plates you would rather not publish.** These pages are public + and get indexed. +- **Keep them under about 500 KB each.** Export as `.webp` at quality 80 if + you can; a 4 MB phone photo will load slowly on mobile data, which is how + most of your visitors arrive. + +## Photographs of individual cars + +The fleet cards on the home page and on `/cars` look for a picture of each +vehicle here, and fall back to the drawing when there is not one. The name of +the file is the only thing that links it to the car — nothing to register, and +no code to change. + +Three names are tried for each car, most specific first: + +| File | Used for | +| -------------------------------- | ------------------------------------------------- | +| `car-maruti-suzuki-swift.webp` | That exact car — its brand and model in the panel | +| `car-swift.webp` | Any Swift, whoever makes it | +| `car-suv.webp` | Any SUV that has no picture of its own | + +The name comes straight from the panel: take the **Brand** and the **Listing +name**, lowercase them, and put a hyphen wherever there is a space. `Maruti +Suzuki` + `Swift` becomes `car-maruti-suzuki-swift`. Change a car's name in the +panel and the filename has to change with it. + +The four body-type files — `car-hatchback`, `car-sedan`, `car-suv`, `car-muv` — +are the quickest way to get a real photograph onto every card: four files, and +nothing is left as a drawing. Photograph the actual cars later and drop them in +one at a time; each one takes over its own card as it arrives. + +These are card headers rather than banners, so they crop to 16:10. About +1200px wide is plenty, and the same shooting notes above apply — especially the +one about number plates. diff --git a/my-app/public/photos/car-brezza--maruti-brezza-self-drive-rental-nagercoil.avif b/my-app/public/photos/car-brezza--maruti-brezza-self-drive-rental-nagercoil.avif new file mode 100644 index 00000000000..61092f15d3d Binary files /dev/null and b/my-app/public/photos/car-brezza--maruti-brezza-self-drive-rental-nagercoil.avif differ diff --git a/my-app/public/robots.txt b/my-app/public/robots.txt new file mode 100644 index 00000000000..cdc37a087cc --- /dev/null +++ b/my-app/public/robots.txt @@ -0,0 +1,44 @@ +# niteshacars.in +# +# Everything a visitor can see is meant to be indexed, so the default is open. +# What is closed is the panel and the parts of the repository that Hostinger's +# Git deployment puts in the web root beside the site. + +User-agent: * +Allow: / + +# The admin panel. Nothing in it is for a search engine: it is a login page, +# the pages behind it, and the endpoints the website reads. Crawling it wastes +# the crawl budget that should be spent on the pages that sell cars, and a +# login form in the index is a page that can only ever rank for the business's +# own name and disappoint whoever clicks it. +# +# The pictures used to be the exception here: they were stored under /admin and +# three Allow lines were all that kept them crawlable. That was a rule standing +# on one leg -- anybody tidying this file, or any tool suggesting the obvious +# "disallow the admin folder", would have taken every photograph on the site +# out of image search without noticing. +# +# They are served from the site root now -- /car-photos/, /site-images/, +# /place-photos/ -- so nothing here has to protect them. The Allow lines stay +# for the old addresses, which are what pages already indexed point at. +Disallow: /admin/ +Allow: /admin/images/ +Allow: /admin/photos/ +Allow: /admin/place-photos/ +Allow: /admin/car-photos/ +Allow: /admin/site-images/ + +# The repository, which is deployed into the web root by this host's Git +# publishing. Apache already refuses these; saying so here saves the crawl. +Disallow: /my-app/ +Disallow: /scripts/ +Disallow: /assets-original/ +Disallow: /marketing/ + +# The diagnostic page. Useful to the owner, meaningless to anyone else, and it +# carries a noindex tag as well -- but a crawler has to fetch a page to read +# that, and there is no reason to make it. +Disallow: /fleet-check.html + +Sitemap: https://niteshacars.in/sitemap.xml diff --git a/my-app/public/self-drive-car-rental-nitesha-cars-and-bikes.webp b/my-app/public/self-drive-car-rental-nitesha-cars-and-bikes.webp new file mode 100644 index 00000000000..dc889c7e3da Binary files /dev/null and b/my-app/public/self-drive-car-rental-nitesha-cars-and-bikes.webp differ diff --git a/my-app/scripts/copy-admin-panel.mjs b/my-app/scripts/copy-admin-panel.mjs new file mode 100644 index 00000000000..7edd4279d6d --- /dev/null +++ b/my-app/scripts/copy-admin-panel.mjs @@ -0,0 +1,52 @@ +// Copies the PHP admin panel into the build output, so it ships with the site. +// +// Hostinger deploys this repository by running `npm run build` and serving +// my-app/dist. That means dist is the document root: anything not in it does +// not exist as far as the web is concerned, which is why niteshacars.in/admin +// returned the site's own "page not found" while the panel sat perfectly well +// in the repository. +// +// Vite copies public/ verbatim into dist/, so the panel could simply live +// there -- but public/ is for the website's own assets, and burying a PHP +// application inside it would be a surprise to anyone reading the tree. This +// copies it explicitly instead, after the build, where the intent is visible. +// +// config.php is never copied. It holds the database password, it is gitignored, +// and dist is rebuilt from scratch on every deploy -- so anything written into +// it is temporary by construction. The panel finds its configuration outside +// the build output; see config() in the panel's src/db.php. + +import { cp, mkdir, rm, stat } from 'node:fs/promises'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const root = dirname(dirname(fileURLToPath(import.meta.url))); +const source = join(dirname(root), 'admin'); +const target = join(root, 'dist', 'admin'); + +// Never ship these. The test tooling is not a security boundary, but it is +// noise on a public server and some of it takes arguments. +const SKIP = new Set(['config.php', '.ftp-deploy-sync-state.json', '.ftp-deploy-admin-state.json']); + +try { + await stat(source); +} catch { + console.error(`copy-admin-panel: no panel at ${source}`); + process.exit(1); +} + +await rm(target, { recursive: true, force: true }); +await mkdir(dirname(target), { recursive: true }); + +let copied = 0; +await cp(source, target, { + recursive: true, + filter: (src) => { + const name = src.slice(src.lastIndexOf('/') + 1); + if (SKIP.has(name)) return false; + copied += 1; + return true; + }, +}); + +console.log(`copy-admin-panel: ${copied} entries into dist/admin`); diff --git a/my-app/scripts/fetch-content.mjs b/my-app/scripts/fetch-content.mjs new file mode 100644 index 00000000000..d80072d5b8e --- /dev/null +++ b/my-app/scripts/fetch-content.mjs @@ -0,0 +1,130 @@ +import { readFile, writeFile } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; +// The same rules the browser applies, so a build and a live page can never +// disagree about which of the panel's edits are safe to show. +import { mergeContent } from '../src/content/merge.mjs'; + +/** + * Bakes the site's editable copy into the build. + * + * The admin panel stores edits; this pulls them in before Vite runs so the + * words end up in the prerendered HTML. Fetching them in the browser instead + * would put every paragraph behind JavaScript, which is the opposite of what + * a site competing on local search wants -- and would take the public site + * down with the admin server. + * + * Never fails the build. If the panel is unreachable, or returns something + * unexpected, the copy committed in defaults.json is used and the build says + * so. A site that will not deploy because a shared host had a bad minute is + * worse than one that deploys last week's wording. + */ + +const here = dirname(fileURLToPath(import.meta.url)); +const app = dirname(here); + +const DEFAULTS = join(app, 'src/content/defaults.json'); +const LIVE = join(app, 'src/content/live.json'); +const BRAND = join(app, 'src/data/brand.json'); +const ADMIN_COPY = join( + app, + '../admin/content-defaults.json', +); + +// The panel used to live on its own subdomain, and this still pointed there +// long after the move. admin.niteshacars.in has no certificate, so every build +// silently failed this fetch and shipped the defaults -- meaning nothing typed +// into "Website content" in the panel ever reached the site, and nothing said +// so louder than one line in a build log. The panel is part of the site now, +// so the address comes from the same place the canonical URLs do. +const { site } = JSON.parse( + await readFile(join(app, 'src/data/seo.json'), 'utf8'), +); + +const API = + process.env.CONTENT_API ?? `${site.origin}/admin/api/public-content.php`; + +const TIMEOUT_MS = 10_000; + +const defaults = JSON.parse(await readFile(DEFAULTS, 'utf8')); + +// The admin server never receives this repository, so it carries its own copy +// of the defaults to prefill the edit form. If the two drift, the form shows +// one thing and the site ships another -- so treat a mismatch as a build +// error rather than a curiosity. +try { + const adminCopy = await readFile(ADMIN_COPY, 'utf8'); + const mine = await readFile(DEFAULTS, 'utf8'); + if (adminCopy !== mine) { + console.error( + 'fetch-content: defaults.json and the admin panel\'s copy differ.\n' + + ' ' + DEFAULTS + '\n' + + ' ' + ADMIN_COPY + '\n' + + 'Copy one over the other so the edit form and the site agree.', + ); + process.exit(1); + } +} catch (err) { + if (err?.code !== 'ENOENT') throw err; + // The admin tree is not always checked out beside the app; that is fine. +} + +let content = defaults; +let brand = {}; +let source = 'defaults (no fetch attempted)'; + +if (process.env.CONTENT_API !== 'off') { + try { + const res = await fetch(API, { + headers: { accept: 'application/json' }, + signal: AbortSignal.timeout(TIMEOUT_MS), + }); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + + const body = await res.json(); + if (!body?.ok) throw new Error('unexpected response shape'); + + // The panel sends only what has been edited; the defaults are already + // here. It used to send both, which is the only thing that made the + // endpoint depend on files a hand-updated panel does not have. + content = mergeContent(defaults, body.overrides ?? {}, + (message) => console.warn(`fetch-content: ${message}`)); + + // Images uploaded in the panel, so the sitemap and the structured data can + // name them. They already reach visitors the moment they are uploaded -- + // the browser asks the panel on every page load -- but a sitemap is written + // once, here, and an image nobody lists is an image image-search will be + // slow to find. Absolute, because that is what both consumers need. + for (const [slot, value] of Object.entries(body.brand ?? {})) { + if (typeof value === 'string' && value !== '') { + brand[slot] = new URL(value, `${API.replace(/\/api\/[^/]*$/, '')}/`).toString(); + } + } + + if (body.ready === false) { + console.warn( + 'fetch-content: the panel has no content table yet, so nothing can be edited.\n' + + ' Open Website content in the admin and press "Set up content storage".', + ); + } + const edited = Object.entries(content).flatMap(([page, sections]) => + Object.keys(sections).filter( + (s) => JSON.stringify(sections[s]) !== JSON.stringify(defaults[page][s]), + ), + ); + source = edited.length + ? `admin panel (${edited.length} section(s) edited: ${edited.join(', ')})` + : 'admin panel (nothing edited yet)'; + } catch (err) { + source = `defaults — could not reach the panel (${err.message})`; + } +} + +await writeFile(LIVE, JSON.stringify(content, null, 2) + '\n'); +await writeFile(BRAND, JSON.stringify(brand, null, 2) + '\n'); + +const uploaded = Object.keys(brand).length; +console.log( + `fetch-content: ${source}` + + (uploaded > 0 ? `, ${uploaded} uploaded image(s)` : ''), +); diff --git a/my-app/scripts/fetch-fleet.mjs b/my-app/scripts/fetch-fleet.mjs new file mode 100644 index 00000000000..c6c1c3dcca5 --- /dev/null +++ b/my-app/scripts/fetch-fleet.mjs @@ -0,0 +1,119 @@ +import { readFile, writeFile } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + +/** + * Bakes the vehicle list into the build. + * + * The fleet lives in the admin panel's database and the site read it from the + * browser, which meant the two pages that sell the business -- /cars and + * /tariff -- were served to Google as "Fetching the current fleet…" and + * "Loading the rate card…". Googlebot does render JavaScript, but it does so + * on a second pass, days later, at its own discretion; and nothing else that + * reads a page does it at all. A site competing for "Swift self drive + * Nagercoil" cannot have the word Swift arrive after the crawl. + * + * So the list is fetched here, before Vite runs, and written into the bundle. + * The browser still asks the panel on load, so a car added this morning still + * appears this morning -- the baked copy is the floor, not the ceiling. + * + * Never fails the build, for the same reason fetch-content.mjs does not: a + * shared host having a bad minute must not stop a deploy. If the panel cannot + * be reached, whatever is already committed in fleet.json stays, and the log + * says so. + * + * A rate of 0 means "not published here". It prints as "On request", never as + * a figure -- a tariff is a promise, and one nobody at the business typed is + * not a promise they can keep. + */ + +const here = dirname(fileURLToPath(import.meta.url)); +const app = dirname(here); + +const SNAPSHOT = join(app, 'src/data/fleet.json'); + +const { site } = JSON.parse(await readFile(join(app, 'src/data/seo.json'), 'utf8')); +const API = process.env.FLEET_API ?? `${site.origin}/admin/api/public-vehicles.php`; + +const TIMEOUT_MS = 10_000; + +const current = JSON.parse(await readFile(SNAPSHOT, 'utf8')); + +/** Only the columns the site renders, and only in the shapes it renders. */ +function clean(v) { + const num = (x) => (typeof x === 'number' && Number.isFinite(x) && x > 0 ? x : 0); + const pick = (x, allowed, fallback) => (allowed.includes(x) ? x : fallback); + + if (!v || typeof v !== 'object') return null; + if (typeof v.id !== 'string' || typeof v.name !== 'string' || v.name.trim() === '') return null; + + return { + id: v.id, + brand: typeof v.brand === 'string' ? v.brand : '', + name: v.name, + bodyType: pick(v.bodyType, ['Hatchback', 'Sedan', 'SUV', 'MUV'], 'Hatchback'), + fuel: pick(v.fuel, ['Petrol', 'Diesel', 'Electric', 'CNG'], 'Petrol'), + transmission: pick(v.transmission, ['Manual', 'Automatic'], 'Manual'), + seats: num(v.seats) || 5, + year: num(v.year), + rateDaily: num(v.rateDaily), + ...(num(v.rateDailyMax) ? { rateDailyMax: num(v.rateDailyMax) } : {}), + ...(num(v.rateWeekly) ? { rateWeekly: num(v.rateWeekly) } : {}), + ...(num(v.rateMonthly) ? { rateMonthly: num(v.rateMonthly) } : {}), + kmLimitPerDay: num(v.kmLimitPerDay), + extraKmRate: num(v.extraKmRate), + deposit: num(v.deposit), + // Deliberately not the photograph. The panel serves those from its own + // URL, which is fine in the browser and wrong in a prerender: the file + // may be gone by the time the page is crawled, and a 404 in the markup + // is worse than the drawing the site falls back to. + available: true, + }; +} + +if (process.env.FLEET_API === 'off') { + console.log('fetch-fleet: skipped, using the committed snapshot'); + process.exit(0); +} + +try { + const res = await fetch(API, { + headers: { accept: 'application/json' }, + signal: AbortSignal.timeout(TIMEOUT_MS), + }); + if (!res.ok) throw new Error(`the server answered ${res.status}`); + + const body = await res.json(); + if (!body?.ok || !Array.isArray(body.vehicles)) { + throw new Error('the response was not the expected { ok, vehicles } shape'); + } + + const vehicles = body.vehicles.map(clean).filter(Boolean); + + // An empty answer is a data question, not a plumbing one -- but overwriting + // a good snapshot with nothing would take the fleet off the prerendered + // pages on the strength of it. The committed list stays and the log asks. + if (vehicles.length === 0) { + console.warn( + 'fetch-fleet: the panel answered with zero vehicles, so the committed ' + + 'snapshot stays. Check that vehicles are set to Available and have a ' + + 'rate card dated today or earlier.', + ); + process.exit(0); + } + + const next = { + note: current.note, + fetched: new Date().toISOString(), + live: true, + vehicles, + }; + + await writeFile(SNAPSHOT, JSON.stringify(next, null, 2) + '\n'); + console.log(`fetch-fleet: ${vehicles.length} vehicles baked in from the panel`); +} catch (error) { + console.warn( + `fetch-fleet: could not read ${API} — ${error?.message ?? 'unknown error'}. ` + + `Keeping the committed snapshot (${current.vehicles.length} vehicles).`, + ); +} diff --git a/my-app/scripts/fetch-places.mjs b/my-app/scripts/fetch-places.mjs new file mode 100644 index 00000000000..4e29eae3835 --- /dev/null +++ b/my-app/scripts/fetch-places.mjs @@ -0,0 +1,76 @@ +import { readFile, writeFile } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + +/** + * Bakes the places list into the build. + * + * Same problem the fleet had, and the same fix. /places was fetched in the + * browser, so the page a crawler was served said "The list is on its way" -- + * on a page whose entire purpose is to be found by somebody searching for + * things to do around Kanyakumari and to sell them a car to get there in. + * + * Never fails the build; keeps what is committed when the panel cannot be + * reached, and says so. + */ + +const here = dirname(fileURLToPath(import.meta.url)); +const app = dirname(here); +const SNAPSHOT = join(app, 'src/data/places.json'); + +const { site } = JSON.parse(await readFile(join(app, 'src/data/seo.json'), 'utf8')); +const API = process.env.PLACES_API ?? `${site.origin}/admin/api/public-places.php`; + +const current = JSON.parse(await readFile(SNAPSHOT, 'utf8')); + +if (process.env.PLACES_API === 'off') { + console.log('fetch-places: skipped, using the committed snapshot'); + process.exit(0); +} + +/** Only the fields the page renders, and only in the shapes it renders. */ +function clean(p) { + if (!p || typeof p !== 'object') return null; + if (typeof p.name !== 'string' || p.name.trim() === '') return null; + return { + id: String(p.id ?? p.name), + name: p.name, + category: typeof p.category === 'string' ? p.category : '', + km: Number.isFinite(p.km) && p.km > 0 ? p.km : undefined, + blurb: typeof p.blurb === 'string' ? p.blurb : '', + // This ends up in an href. The panel refuses anything but http(s), and the + // check is repeated because a link that can carry javascript: is a hole + // wherever it is filled from. + mapUrl: /^https?:\/\//i.test(p.mapUrl ?? '') ? p.mapUrl : '', + }; +} + +try { + const res = await fetch(API, { + headers: { accept: 'application/json' }, + signal: AbortSignal.timeout(10_000), + }); + if (!res.ok) throw new Error(`the server answered ${res.status}`); + + const body = await res.json(); + if (!body?.ok || !Array.isArray(body.places)) { + throw new Error('the response was not the expected { ok, places } shape'); + } + + const places = body.places.map(clean).filter(Boolean); + if (places.length === 0) { + console.warn('fetch-places: the panel answered with none, so the committed list stays'); + process.exit(0); + } + + await writeFile( + SNAPSHOT, + JSON.stringify({ note: current.note, fetched: new Date().toISOString(), live: true, places }, null, 2) + '\n', + ); + console.log(`fetch-places: ${places.length} places baked in from the panel`); +} catch (error) { + console.warn( + `fetch-places: could not read ${API} — ${error?.message ?? 'unknown error'}. ` + + `Keeping the committed list (${current.places.length} places).`, + ); +} diff --git a/my-app/scripts/photo-manifest.mjs b/my-app/scripts/photo-manifest.mjs new file mode 100644 index 00000000000..4e26686a2b2 --- /dev/null +++ b/my-app/scripts/photo-manifest.mjs @@ -0,0 +1,91 @@ +// Records which photographs actually exist, so the site can use one where it +// has one and fall back to a drawing where it does not. +// +// Doing this at build time rather than in the browser matters: an whose +// file is missing does not fall back, it renders a broken frame on a live +// page. The only way to choose correctly is to know before rendering, and the +// only place that knows is the filesystem. +// +// Drop a file into public/photos and the next build picks it up. Nothing to +// edit, nothing to register -- which is the point, because a manifest someone +// has to remember to update is a manifest that goes stale. + +import { readdirSync, writeFileSync, mkdirSync, readFileSync } from 'node:fs'; +import { dirname, extname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const root = dirname(dirname(fileURLToPath(import.meta.url))); +const photoDir = join(root, 'public', 'photos'); +const outFile = join(root, 'src', 'data', 'photos.json'); + +// Formats a browser will actually decode. A .heic straight off an iPhone is a +// real file and a broken image, so it is not offered to the page. +const USABLE = new Set(['.jpg', '.jpeg', '.png', '.webp', '.avif']); + +let entries = []; +try { + entries = readdirSync(photoDir, { withFileTypes: true }); +} catch { + mkdirSync(photoDir, { recursive: true }); +} + +const photos = {}; +const skipped = []; + +for (const entry of entries) { + if (!entry.isFile()) continue; + const ext = extname(entry.name).toLowerCase(); + if (!USABLE.has(ext)) { + if (!entry.name.startsWith('.') && entry.name !== 'README.md') skipped.push(entry.name); + continue; + } + // The key is the filename without its extension, so `cars-hero.jpg` and + // `cars-hero.webp` are the same slot -- swapping format is not a rename job. + // + // Anything after a double hyphen is words for the address, not part of the + // slot: `home-hero--self-drive-car-rental-kanyakumari.webp` fills the + // `home-hero` slot. The two pull in opposite directions otherwise -- a slot + // wants a short stable key and a URL wants to say what the picture is, and + // Google reads the file name as one of the few things it knows about an + // image besides its alt text. This way the file name does both, and a + // rewording is not a slot change. + const key = entry.name.slice(0, -ext.length).split('--')[0]; + // A later format wins only if nothing claimed the slot, so replacing a .jpg + // with a .webp without deleting the .jpg does not produce a coin flip. + const rank = ['.avif', '.webp', '.jpg', '.jpeg', '.png'].indexOf(ext); + const current = photos[key]; + if (!current || rank < current.rank) { + photos[key] = { rank, path: `/photos/${entry.name}` }; + } +} + +const manifest = Object.fromEntries( + Object.entries(photos) + .sort(([a], [b]) => a.localeCompare(b)) + .map(([key, v]) => [key, v.path]), +); + +mkdirSync(dirname(outFile), { recursive: true }); +const next = JSON.stringify(manifest, null, 2) + '\n'; + +// Only write when something changed. Rewriting an identical file on every dev +// start would retrigger Vite's watcher and loop. +let prev = null; +try { + prev = readFileSync(outFile, 'utf8'); +} catch { + /* first run */ +} +if (prev !== next) writeFileSync(outFile, next); + +const count = Object.keys(manifest).length; +console.log( + `photo-manifest: ${count} photograph${count === 1 ? '' : 's'}` + + (count ? ` (${Object.keys(manifest).join(', ')})` : ' — using the drawn scenes'), +); +if (skipped.length) { + console.warn( + `photo-manifest: ignored ${skipped.join(', ')} — browsers cannot display these; ` + + 'save as .jpg, .webp or .avif', + ); +} diff --git a/my-app/scripts/prerender-seo.mjs b/my-app/scripts/prerender-seo.mjs new file mode 100644 index 00000000000..4784b5dd46a --- /dev/null +++ b/my-app/scripts/prerender-seo.mjs @@ -0,0 +1,1020 @@ +/** + * Per-route HTML and a sitemap, written after the Vite build. + * + * The app renders in the browser, so every route would otherwise be served the + * same index.html carrying the home page's title and description. Google + * executes JavaScript and would eventually see the right ones, but WhatsApp, + * Facebook, X and most other link unfurlers do not run it at all -- they read + * the HTML as served and stop. Sharing niteshacars.in/tariff would preview as + * the home page. + * + * So each route gets its own directory with its own index.html: same bundle, + * same markup, but title, description, canonical and og: tags rewritten. Pages + * serves /tariff/ from /tariff/index.html, and a request for /tariff without + * the slash is redirected to it. + * + * dist/404.html stays a copy of the root page: it is what Pages serves for + * anything unmatched, and the router resolves the URL once the app boots. + */ +import { readFileSync, readdirSync, writeFileSync, mkdirSync, copyFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { pathToFileURL } from 'node:url'; +import { createHash } from 'node:crypto'; +import { allTownRoutes, TOWN_BASE } from '../src/data/town-routes.mjs'; +import { modelRoutes, MODEL_BASE } from '../src/data/model-routes.mjs'; +import { serviceAreaRoutes, serviceAreaPath } from '../src/data/service-area-routes.mjs'; +import { postRoutes, BLOG_BASE } from '../src/data/post-routes.mjs'; + +const root = join(import.meta.dirname, '..'); + +/** + * The app, rendered to HTML here rather than in the visitor's browser. + * + * Every route was served the same empty
, so nothing was on + * screen until 300 KB of JavaScript had been fetched, parsed and run -- three + * seconds on a mid-range phone on a rural connection, which is most of the + * traffic this site gets. Everything a page is scored on happens inside that + * window. + * + * Built by `vite build --ssr` into dist-ssr immediately before this runs. If + * it is not there the pages are still written, just empty the way they were: + * the metadata and the sitemap are the job this script existed for, and losing + * them because the extra build step failed would be the worse trade. + */ +let renderRoute = null; +try { + ({ render: renderRoute } = await import( + pathToFileURL(join(root, 'dist-ssr/entry-server.js')).href + )); +} catch (error) { + console.warn( + `prerender-seo: no server build to render with (${error.message}) -- ` + + 'writing the pages without their markup', + ); +} +const dist = join(root, 'dist'); +const seo = JSON.parse(readFileSync(join(root, 'src/data/seo.json'), 'utf8')); + +// One route per town, derived from towns.json by the same module the app uses, +// so a town added there gets its page, its tags and its line in the sitemap +// without a second list being edited. +const towns = JSON.parse(readFileSync(join(root, 'src/data/towns.json'), 'utf8')); +// And one per vehicle people ask for by name, for the same reason. +const models = JSON.parse(readFileSync(join(root, 'src/data/models.json'), 'utf8')); +// The vehicle list as baked in by fetch-fleet.mjs, so a car page's structured +// data can carry the rate when the panel has published one. +const fleet = JSON.parse(readFileSync(join(root, 'src/data/fleet.json'), 'utf8')); +// And one per service-in-a-town pair worth having a page of its own. +const serviceAreas = JSON.parse( + readFileSync(join(root, 'src/data/service-areas.json'), 'utf8'), +); +// And one per article. +const blog = JSON.parse(readFileSync(join(root, 'src/data/posts.json'), 'utf8')); +const routes = [ + ...seo.routes, + ...allTownRoutes(towns.towns, seo.site), + ...modelRoutes(models.models), + ...serviceAreaRoutes(serviceAreas.pages), + ...postRoutes(blog.posts), +]; +// A title or a description longer than a search result shows is not wrong, +// it is just cut -- and the part that gets cut is the part written last, +// which is usually the towns. Both numbers are where Google starts trimming +// in a desktop result. Worth failing the build over: these live in a +// committed file, the fix is a shorter sentence, and nobody reviews a +// warning. +const tooLong = routes + .map((r) => [r.path, r.title.length > 62 && `title ${r.title.length}`, + r.description.length > 160 && `description ${r.description.length}`]) + .map(([path, ...bad]) => [path, bad.filter(Boolean)]) + .filter(([, bad]) => bad.length > 0); +if (tooLong.length > 0) { + throw new Error( + 'prerender-seo: these would be cut in a search result:\n' + + tooLong.map(([path, bad]) => ` ${path}: ${bad.join(', ')}`).join('\n'), + ); +} + +let template = readFileSync(join(dist, 'index.html'), 'utf8'); + +// The icons, addressed by a hash of themselves. +// +// A browser caches a favicon by its address and holds on to it far harder +// than it holds a page: Chrome keeps one across ordinary reloads and reads +// the new file only when its own store forgets, which can be months. The +// owner changed the icon and still had the one this project shipped with in +// his tab, because /favicon.svg is the same address it has always been. +// +// Stamped with the file's own hash, a changed icon is a changed address, so +// nothing cached can match it and nobody has to know to clear anything. +for (const icon of ['favicon.svg', 'apple-touch-icon.png']) { + const stamp = createHash('sha1') + .update(readFileSync(join(dist, icon))) + .digest('hex') + .slice(0, 8); + template = template.replaceAll(`/${icon}"`, `/${icon}?v=${stamp}"`); +} +// Which photographs actually exist, for the sitemap and the structured data. +const photos = JSON.parse(readFileSync(join(root, 'src/data/photos.json'), 'utf8')); + +/** + * The business's own details, as the owner has them in the panel. + * + * The address, the opening hours and the social links are all editable under + * Website content, and they are also three of the things Google reads out of a + * page's structured data to build the panel that appears beside a local search + * result. Typing them twice -- once for visitors and once for a crawler -- is + * how they end up disagreeing, and a business whose hours differ between its + * own page and its structured data is one Google trusts less, not more. + * + * So there is one copy. live.json is written by fetch-content.mjs just before + * this runs, from the panel. + */ +const live = JSON.parse(readFileSync(join(root, 'src/content/live.json'), 'utf8')); +const footer = live?.home?.footer ?? {}; +const social = live?.home?.social ?? {}; + +// Images uploaded in the panel, written by fetch-content.mjs just before this +// runs. Merged over the committed ones because that is the order the site +// itself resolves them in -- an upload wins over a file in the repository, so +// the sitemap must say the same thing the page shows. +let uploaded = {}; +try { + uploaded = JSON.parse(readFileSync(join(root, 'src/data/brand.json'), 'utf8')); +} catch { + // Not written yet, which is the normal state of a fresh checkout. +} + +/** + * The share card a route asks for, or nothing. + * + * Either a slot name, resolved like any other picture, or a path from the site + * root for a file that is only ever a share card. + */ +function shareImageFor(route) { + const want = route.shareImage; + if (!want) return undefined; + if (want.startsWith('/')) return seo.site.origin + want; + return pictureFor(want); +} + +/** + * Where a route's picture actually is, or nothing. + * + * An upload wins, then a file in public/photos named after the slot, then a + * path the route names outright. That last one is for a picture that is not a + * slot at all -- the home banner is a file at the site root, shared with the + * share card, and without this the page would show a photograph while the + * sitemap listed none for it. + */ +function pictureFor(slot, fallback) { + // Uploaded URLs are already absolute; committed ones are site-relative. + if (slot && typeof uploaded[slot] === 'string') return uploaded[slot]; + if (slot && typeof photos[slot] === 'string') return seo.site.origin + photos[slot]; + return fallback ? seo.site.origin + fallback : undefined; +} + +/** Replace the content of a meta/title/canonical tag, leaving the rest alone. */ +function rewrite(html, { title, description, url, picture, alt }) { + const out = html + .replace(/[\s\S]*?<\/title>/, `<title>${title}`) + .replace( + /(/g, + '', + ); +} + +const escape = (s) => + s.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); + +/** + * Corrects the business name and adds the site's photographs to the structured + * data already in the template. + * + * index.html has carried an AutoRental block for a while. I missed it -- the + * grep that went looking covered src/ and scripts/ and not the template -- and + * added a second one, which put two AutoRental entities with different names on + * every page. That is worse than having none: it asks Google to decide which of + * two businesses this is. + * + * So this edits the block that exists rather than writing another. What it adds + * is the part worth adding for image search: every banner photograph that + * actually exists, and the logo, as absolute URLs. + */ +function enrichJsonLd(html) { + const pattern = /`, + ); +} + +/** + * The services worth naming, and the words to name them with. + * + * Deliberately the phrases somebody would type rather than the site's own + * headings: "self drive car rental" is what is searched for, "What we hire" + * is what the page calls the list of them. + */ +const SERVICE_ROUTES = new Map([ + ['/cars', 'Self drive car rental'], + ['/bikes', 'Bike and scooty rental'], + ['/wedding-cars', 'Wedding car rental'], + ['/tourist-vehicles', 'Tourist vehicle hire with a driver'], + ['/monthly', 'Monthly and long term car rental'], +]); + +/** The town a route is about, or null for the pages that are not about one. */ +function townFor(route) { + if (!route.path.startsWith(TOWN_BASE + '/')) return null; + const slug = route.path.slice(TOWN_BASE.length + 1); + return towns.towns.find((t) => t.slug === slug) ?? null; +} + +/** The article a route is about, or null. */ +function postFor(route) { + if (!route.path.startsWith(BLOG_BASE + '/')) return null; + const slug = route.path.slice(BLOG_BASE.length + 1); + return blog.posts.find((post) => post.slug === slug) ?? null; +} + +/** The service-in-a-town page a route is about, or null. */ +function serviceAreaFor(route) { + return serviceAreas.pages.find((page) => serviceAreaPath(page) === route.path) ?? null; +} + +/** The car a route is about, or null for the pages that are not about one. */ +function modelFor(route) { + if (!route.path.startsWith(MODEL_BASE + '/')) return null; + const slug = route.path.slice(MODEL_BASE.length + 1); + return models.models.find((m) => m.slug === slug) ?? null; +} + +/** + * The trail from the home page to this one, as Google shows it. + * + * A search result used to print the bare URL under the title. With this it + * prints "niteshacars.in > Wedding car rental", which says what the page is + * before anyone has clicked, and it is one of the few pieces of structured + * data that still changes what a result looks like. + * + * The home page gets none: a breadcrumb trail of one item is noise. + */ +function breadcrumbFor(route) { + if (route.path === '/') return null; + + // The route's own title, cut at the business name or the place, whichever + // comes first. A breadcrumb is read at a glance under a search result, and + // "Wedding Car Rental in Nagercoil & Kanyakumari — NiteSha Cars & Bikes" is + // not a trail anyone reads; "Wedding Car Rental" is. The town is already in + // the title above it and in the description below. + // + // Except on a town page, where the town IS the leaf. Cutting at " in " there + // threw away the only word that distinguishes one of these pages from the + // other eleven, and left twelve trails all ending "Self Drive Car Rental". + const town = townFor(route); + const model = modelFor(route); + const area = serviceAreaFor(route); + const post = postFor(route); + const leaf = town + ? town.name + : model + ? model.name + : area + ? area.town + : post + ? post.title + : route.path === TOWN_BASE + ? 'Where we deliver' + : route.title.split(/\s—\s|\sin\s/)[0].trim(); + + const trail = [ + { '@type': 'ListItem', position: 1, name: 'Home', item: seo.site.origin + '/' }, + ]; + + // A town page sits under the hub, and saying so is the point of a trail + // rather than a pair: "niteshacars.in > Where we deliver > Marthandam" tells + // someone reading a result that this is one town of several, which is true + // and is what stops the page looking like a site of its own. + if (route.path.startsWith(TOWN_BASE + '/')) { + trail.push({ + '@type': 'ListItem', + position: 2, + name: 'Where we deliver', + item: seo.site.origin + TOWN_BASE, + }); + } + + // And a car page sits under the listing every car appears on, for the same + // reason: "niteshacars.in > Our cars > Maruti Suzuki Swift" says this is one + // of a fleet rather than the whole of a site. + if (route.path.startsWith(MODEL_BASE + '/')) { + trail.push({ + '@type': 'ListItem', + position: 2, + name: 'Our cars', + item: seo.site.origin + MODEL_BASE, + }); + } + + // An article sits under the blog. + if (post) { + trail.push({ + '@type': 'ListItem', + position: 2, + name: 'Blog', + item: seo.site.origin + BLOG_BASE, + }); + } + + // A service-in-a-town page sits under its service: "niteshacars.in > + // Bike and scooter rental > Kanyakumari". + if (area) { + trail.push({ + '@type': 'ListItem', + position: 2, + name: area.service, + item: seo.site.origin + area.base, + }); + } + + trail.push({ + '@type': 'ListItem', + position: trail.length + 1, + name: leaf, + item: seo.site.origin + route.path, + }); + + return { '@context': 'https://schema.org', '@type': 'BreadcrumbList', itemListElement: trail }; +} + +/** + * What is offered, and where -- for a town page only. + * + * The business block on every page says the whole district. This says one + * town, on the page about that town, which is the specific claim a search for + * "self drive car Marthandam" is trying to match. provider points at the + * business's @id rather than repeating it, so there is still one business. + */ +function townServiceFor(route) { + // A service-in-a-town page makes the same claim, but narrower: this one + // service, in this one town, which is exactly the search it answers. + const area = serviceAreaFor(route); + if (area) { + return { + '@context': 'https://schema.org', + '@type': 'Service', + name: `${area.service} in ${area.town}`, + serviceType: area.service, + provider: { '@id': seo.site.origin + '#business' }, + areaServed: { + '@type': 'City', + name: area.town, + containedInPlace: { + '@type': 'AdministrativeArea', + name: `${seo.site.district} district`, + }, + }, + url: seo.site.origin + route.path, + }; + } + + const town = townFor(route); + if (!town) return null; + + return { + '@context': 'https://schema.org', + '@type': 'Service', + name: `Self drive car and bike rental in ${town.name}`, + serviceType: 'Self drive vehicle rental', + provider: { '@id': seo.site.origin + '#business' }, + areaServed: { + '@type': 'City', + name: town.name, + containedInPlace: { + '@type': 'AdministrativeArea', + name: `${seo.site.district} district`, + }, + }, + url: seo.site.origin + route.path, + }; +} + +/** + * The questions and answers the page shows, as structured data. + * + * Only on the pages that show them. Google asks that FAQPage data match + * visible content, and a page claiming answers it does not display is the + * kind of thing that gets a site's rich results turned off rather than + * improved -- so this reads the same content the component renders, and is + * attached to the same routes it is rendered on. + */ +const FAQ_ROUTES = new Set(['/', '/tariff']); + +function faqFor(route) { + // A car page asks and answers its own three, and renders every one of them, + // so the same rule is satisfied by reading them from the same file the page + // does. + const model = modelFor(route); + if (model) { + return { + '@context': 'https://schema.org', + '@type': 'FAQPage', + '@id': seo.site.origin + route.path + '#faq', + mainEntity: model.faq.map((item) => ({ + '@type': 'Question', + name: item.question, + acceptedAnswer: { '@type': 'Answer', text: item.answer }, + })), + }; + } + + // As do the service-in-a-town pages, which render theirs in full. + const area = serviceAreaFor(route); + if (area) { + return { + '@context': 'https://schema.org', + '@type': 'FAQPage', + '@id': seo.site.origin + route.path + '#faq', + mainEntity: area.faq.map((item) => ({ + '@type': 'Question', + name: item.question, + acceptedAnswer: { '@type': 'Answer', text: item.answer }, + })), + }; + } + + if (!FAQ_ROUTES.has(route.path)) return null; + + const items = (live?.home?.faq?.items ?? []) + .filter((item) => String(item?.question ?? '').trim() && String(item?.answer ?? '').trim()); + if (items.length === 0) return null; + + return { + '@context': 'https://schema.org', + '@type': 'FAQPage', + '@id': seo.site.origin + route.path + '#faq', + mainEntity: items.map((item) => ({ + '@type': 'Question', + name: String(item.question).trim(), + acceptedAnswer: { '@type': 'Answer', text: String(item.answer).trim() }, + })), + }; +} + +/** + * The vehicle a car page is about. + * + * schema.org has a type for this and the page is entirely about one car, so + * saying so costs a few hundred bytes and tells a crawler that "Rumion" is a + * seven-seat petrol vehicle rather than a word in a heading. + * + * No price attached. The rate lives in the panel, it is not always published, + * and an offer carrying a figure nobody typed is worse than an offer missing. + * When the panel does carry one the build has it here, and it is quoted per + * day, which is the unit a rental is actually offered in. + */ +function vehicleFor(route) { + const model = modelFor(route); + if (!model) return null; + + const data = { + '@context': 'https://schema.org', + '@type': 'Car', + '@id': seo.site.origin + route.path + '#vehicle', + name: model.name, + url: seo.site.origin + route.path, + description: model.intro, + vehicleConfiguration: model.bodyType, + fuelType: model.fuel, + vehicleTransmission: model.transmission, + vehicleSeatingCapacity: { + '@type': 'QuantitativeValue', + value: model.seats, + }, + }; + + const brand = String(model.name).split(' ')[0]; + if (brand) data.brand = { '@type': 'Brand', name: brand }; + + const car = fleet.vehicles.find((v) => + `${v.brand} ${v.name}`.toLowerCase().includes(model.match.toLowerCase()), + ); + if (car && car.rateDaily > 0) { + data.offers = { + '@type': 'Offer', + availability: 'https://schema.org/InStock', + priceCurrency: 'INR', + priceSpecification: { + '@type': 'UnitPriceSpecification', + price: car.rateDaily, + priceCurrency: 'INR', + unitCode: 'DAY', + referenceQuantity: { '@type': 'QuantitativeValue', value: 1, unitCode: 'DAY' }, + }, + seller: { '@id': seo.site.origin + '#business' }, + url: seo.site.origin + route.path, + }; + } + + return data; +} + +/** + * The article itself. + * + * Google does not give rich results to a blog post the way it does to a + * recipe, so this is not decoration: it is what says the page is an article + * with a date and an author rather than another service page, which is the + * difference between it being read as editorial and as another sales page on + * the same site. dateModified is the honest one -- an article that claims to + * be fresh and is not gets treated accordingly. + */ +function articleFor(route) { + const post = postFor(route); + if (!post) return null; + + return { + '@context': 'https://schema.org', + '@type': 'BlogPosting', + '@id': seo.site.origin + route.path + '#article', + headline: post.title, + description: post.description, + datePublished: post.date, + dateModified: post.updated ?? post.date, + inLanguage: 'en-IN', + mainEntityOfPage: { '@type': 'WebPage', '@id': seo.site.origin + route.path }, + // #business rather than #organization: the AutoRental block is on every + // page, so this reference resolves on the page it is read from. The + // Organization node is on the home page alone, and a reference to an @id + // that is not on the page is a reference a parser may not follow. + // AutoRental is a LocalBusiness is an Organization, so it is a valid + // author and publisher either way. + author: { '@id': seo.site.origin + '#business' }, + publisher: { '@id': seo.site.origin + '#business' }, + ...(pictureFor('blog-hero') ? { image: pictureFor('blog-hero') } : {}), + }; +} + +/** + * Who publishes this site, and what the site is -- on the home page only. + * + * The AutoRental block on every page says what the business does. These two + * say who it is and what this domain is, which is what a knowledge panel is + * assembled from and what lets a crawler treat the logo, the name and the + * social profiles as belonging together. + * + * Home page only, deliberately. Repeating an Organization on thirty-two pages + * does not make it thirty-two times truer; the @id is what the other blocks + * point at. + */ +function publisherFor(route) { + if (route.path !== '/') return null; + + const org = { + '@context': 'https://schema.org', + '@type': 'Organization', + '@id': seo.site.origin + '#organization', + name: seo.site.name, + url: seo.site.origin + '/', + telephone: seo.site.phone, + email: seo.site.email, + areaServed: { + '@type': 'AdministrativeArea', + name: `${seo.site.district} district, ${seo.site.region}`, + }, + }; + + const logo = pictureFor('logo'); + if (logo) org.logo = { '@type': 'ImageObject', url: logo }; + + const profiles = [social.facebook, social.instagram, social.youtube, social.linkedin] + .map((v) => String(v ?? '').trim()) + .filter((v) => /^https?:\/\//.test(v)); + if (profiles.length > 0) org.sameAs = profiles; + + return [ + org, + { + '@context': 'https://schema.org', + '@type': 'WebSite', + '@id': seo.site.origin + '#website', + name: seo.site.name, + url: seo.site.origin + '/', + inLanguage: 'en-IN', + publisher: { '@id': seo.site.origin + '#organization' }, + }, + ]; +} + +/** + * The chunk each route's page compiles to, so the prerendered HTML can ask + * for it up front. + * + * Pages are lazy(), which is right -- the entry bundle should not carry + * eighteen pages. But it means hydration reaches a route whose chunk has not + * arrived, suspends, and replaces the prerendered markup with the Suspense + * fallback until it does. The page that was already on screen collapses to a + * navy band and comes back a third of a second later: 0.19 CLS on every inner + * page, and the whole point of prerendering undone at the last step. + * + * A modulepreload alongside the entry script means the chunk is in the module + * map before React looks for it, so there is nothing to wait for. + * + * The map is by hand because the routes come from five different files and + * the chunk names come from Vite. It is checked below rather than trusted: a + * route missing from it only loses its preload, which is the behaviour this + * replaced, so drift costs speed and never correctness. + */ +const PAGE_MODULE = new Map([ + ['/', 'src/pages/Home.tsx'], + ['/about', 'src/pages/About.tsx'], + ['/cars', 'src/pages/Cars.tsx'], + ['/bikes', 'src/pages/Bikes.tsx'], + ['/wedding-cars', 'src/pages/WeddingCars.tsx'], + ['/tourist-vehicles', 'src/pages/TouristVehicles.tsx'], + ['/monthly', 'src/pages/Monthly.tsx'], + ['/nri', 'src/pages/Nri.tsx'], + ['/tariff', 'src/pages/Tariff.tsx'], + ['/blog', 'src/pages/Blog.tsx'], + ['/contact', 'src/pages/Contact.tsx'], + ['/places', 'src/pages/Places.tsx'], + ['/services', 'src/pages/Services.tsx'], + [TOWN_BASE, 'src/pages/CarRentalAreas.tsx'], +]); + +/** The generated families, matched by prefix after the fixed paths. */ +const PAGE_MODULE_BY_PREFIX = [ + [TOWN_BASE + '/', 'src/pages/Town.tsx'], + [MODEL_BASE + '/', 'src/pages/CarModel.tsx'], + [BLOG_BASE + '/', 'src/pages/Post.tsx'], +]; + +let manifest = null; +try { + manifest = JSON.parse(readFileSync(join(dist, '.vite/manifest.json'), 'utf8')); +} catch { + console.warn( + 'prerender-seo: no build manifest, so no route chunks are preloaded -- ' + + 'pages will hydrate through the Suspense fallback', + ); +} + +function moduleFor(route) { + const fixed = PAGE_MODULE.get(route.path); + if (fixed) return fixed; + + const prefixed = PAGE_MODULE_BY_PREFIX.find(([prefix]) => route.path.startsWith(prefix)); + if (prefixed) return prefixed[1]; + + // Everything else is a service-in-a-town page, which lives under its own + // service (/bikes/kanyakumari) and so cannot be matched by one prefix. + return serviceAreaFor(route) ? 'src/pages/ServiceArea.tsx' : null; +} + +/** for this route's chunk, and its CSS if it has any. */ +function preloadFor(route) { + if (!manifest) return ''; + + const source = moduleFor(route); + if (!source) { + console.warn(`prerender-seo: ${route.path} has no page module mapped -- not preloaded`); + return ''; + } + + const entry = manifest[source]; + if (!entry?.file) return ''; + + const links = [`\n `]; + for (const css of entry.css ?? []) { + links.push(`\n `); + } + return links.join(''); +} + +let written = 0;for (const route of routes) { + const url = seo.site.origin + route.path; + let html = rewrite(template, { + title: escape(route.title), + description: escape(route.description), + url, + // Only when the route names one. A banner is not a share card. + picture: shareImageFor(route), + alt: route.imageAlt ?? route.title, + }); + + // Immediately before , which is the last point the parser reaches + // before the body. + // + // The banner photograph does not need one here. React emits a preload for an + // image marked fetchPriority="high" as it renders, with the exact URL the + // ends up using -- and a second preload written by hand with the + // absolute form of the same URL is a second download of the same file, which + // is what the first version of this did. + const extra = [ + breadcrumbFor(route), + townServiceFor(route), + vehicleFor(route), + articleFor(route), + faqFor(route), + ...(publisherFor(route) ?? []), + ] + .filter(Boolean) + .map((node) => `\n `) + .join(''); + + const head = preloadFor(route) + extra; + if (head !== '') { + html = html.replace('', () => `${head}\n `); + } + + if (renderRoute) { + const body = await renderRoute(route.path); + + // A page delivered inside