Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ jobs:
- name: Format check
run: pnpm format:check

- name: License headers
run: pnpm run license:check

- name: Type check
run: pnpm tsc --noEmit

Expand Down
4 changes: 4 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
AG Tech Web Template
Copyright 2026 AG Technology Group LLC

This product includes software developed by AG Technology Group LLC.
3 changes: 3 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

import js from "@eslint/js"
import globals from "globals"
import reactHooks from "eslint-plugin-react-hooks"
Expand Down
3 changes: 3 additions & 0 deletions orval.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

import { defineConfig } from "orval"

const input = process.env.OPENAPI_URL || "http://localhost:8000/openapi.json"
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
"lint": "eslint .",
"format": "prettier --write .",
"format:check": "prettier --check .",
"license:check": "node scripts/license-header.mjs --check --all",
"license:fix": "node scripts/license-header.mjs --fix --all",
"preview": "vite preview",
"test": "vitest",
"test:run": "vitest run",
Expand Down
117 changes: 117 additions & 0 deletions scripts/license-header.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#!/usr/bin/env node
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

/**
* license-header.mjs — stamp or verify SPDX license headers on source files.
*
* Usage:
* node scripts/license-header.mjs --check --all verify every tracked source file (CI)
* node scripts/license-header.mjs --fix --all stamp every tracked source file
* node scripts/license-header.mjs --fix <files...> stamp specific files (lint-staged)
*
* Default mode is --check. Re-running is safe: a file already carrying an
* SPDX identifier in its first lines is left untouched (idempotent), so the
* lint-staged auto-fix and the CI check can never disagree.
*/

import { execFileSync } from "node:child_process"
import { readFileSync, writeFileSync } from "node:fs"

const HEADER = [
"// SPDX-FileCopyrightText: 2026 AG Technology Group LLC",
"// SPDX-License-Identifier: Apache-2.0",
]

const SOURCE_EXT = /\.(ts|tsx|js|jsx)$/
// Vendored deps, build output, and the Orval-generated client are never stamped.
const SKIP_PATH = [
/(^|\/)node_modules\//,
/(^|\/)dist\//,
/(^|\/)build\//,
/(^|\/)\.next\//,
/(^|\/)coverage\//,
/(^|\/)src\/api\/generated\//,
]
const GENERATED_NAME = /\.gen\.(ts|tsx|js|jsx)$/
const GENERATED_BANNER = /@generated|do not edit|automatically generated/i

// Returns "skip" (with reason), "stamped" (already has a header), or "needs".
function classify(file, content) {
if (!SOURCE_EXT.test(file))
return { status: "skip", reason: "not a source file" }
if (SKIP_PATH.some((re) => re.test(file)))
return { status: "skip", reason: "vendored/output path" }
if (GENERATED_NAME.test(file))
return { status: "skip", reason: "generated (*.gen.*)" }
const lines = content.split("\n")
if (GENERATED_BANNER.test(lines.slice(0, 15).join("\n")))
return { status: "skip", reason: "generated banner" }
if (lines.slice(0, 10).join("\n").includes("SPDX-License-Identifier"))
return { status: "stamped" }
return { status: "needs" }
}

// Insert the header at the very top — above triple-slash directives (a plain
// comment before `/// <reference>` keeps the directive valid) — but below a
// shebang if present. Leaves exactly one blank line before the original code.
function applyHeader(content) {
const lines = content.split("\n")
const shebang = lines[0]?.startsWith("#!") ? lines.shift() : null
while (lines.length && lines[0].trim() === "") lines.shift()
const out = []
if (shebang) out.push(shebang)
out.push(...HEADER, "", ...lines)
const result = out.join("\n")
return result.endsWith("\n") ? result : result + "\n"
}

const args = process.argv.slice(2)
const fix = args.includes("--fix")
const explicit = args.filter((a) => !a.startsWith("--"))
const targets = args.includes("--all")
? execFileSync("git", ["ls-files", "*.ts", "*.tsx", "*.js", "*.jsx"], {
encoding: "utf8",
})
.split("\n")
.filter(Boolean)
: explicit

const stamped = []
const already = []
const skipped = []
const missing = []

for (const file of targets) {
let content
try {
content = readFileSync(file, "utf8")
} catch {
continue // file was deleted between staging and run
}
const c = classify(file, content)
if (c.status === "skip") skipped.push(`${file} (${c.reason})`)
else if (c.status === "stamped") already.push(file)
else if (fix) {
writeFileSync(file, applyHeader(content))
stamped.push(file)
} else missing.push(file)
}

if (fix) {
console.log(
`license-header: stamped ${stamped.length}, already ${already.length}, skipped ${skipped.length}`
)
for (const f of stamped) console.log(` + ${f}`)
} else if (missing.length) {
console.error(
`license-header: ${missing.length} file(s) missing an SPDX header:`
)
for (const f of missing) console.error(` - ${f}`)
console.error('Run "pnpm run license:fix" to add them.')
process.exit(1)
} else {
console.log(
`license-header: OK — ${already.length} stamped, ${skipped.length} skipped`
)
}
3 changes: 3 additions & 0 deletions src/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

import ky, { type Options } from "ky"

export const baseUrl = import.meta.env.VITE_API_URL || "/api"
Expand Down
3 changes: 3 additions & 0 deletions src/api/handlers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

import { http, HttpResponse, type HttpHandler } from "msw"

/**
Expand Down
3 changes: 3 additions & 0 deletions src/api/orval-client.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

import { api } from "./api"

/**
Expand Down
3 changes: 3 additions & 0 deletions src/components/error-boundary.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

import { Link } from "@tanstack/react-router"
import type { ErrorComponentProps } from "@tanstack/react-router"
import { Button } from "@/components/ui/button"
Expand Down
3 changes: 3 additions & 0 deletions src/components/not-found.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

import { Link } from "@tanstack/react-router"
import { Button } from "@/components/ui/button"

Expand Down
3 changes: 3 additions & 0 deletions src/components/theme-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

import { createContext, useContext, useEffect, useState } from "react"

type Theme = "dark" | "light" | "system"
Expand Down
3 changes: 3 additions & 0 deletions src/components/theme-toggle.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

import { Moon, Monitor, Sun } from "lucide-react"
import { Button } from "@/components/ui/button"
import { useTheme } from "@/components/theme-provider"
Expand Down
3 changes: 3 additions & 0 deletions src/components/ui/button.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
Expand Down
3 changes: 3 additions & 0 deletions src/components/ui/card.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

import * as React from "react"

import { cn } from "@/lib/utils"
Expand Down
3 changes: 3 additions & 0 deletions src/components/ui/input.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

import * as React from "react"

import { cn } from "@/lib/utils"
Expand Down
3 changes: 3 additions & 0 deletions src/components/ui/label.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"

Expand Down
3 changes: 3 additions & 0 deletions src/components/ui/skeleton.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

import * as React from "react"

import { cn } from "@/lib/utils"
Expand Down
3 changes: 3 additions & 0 deletions src/lib/analytics.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

import { createContext, useContext, useMemo } from "react"
import { logger } from "@/lib/logger"

Expand Down
3 changes: 3 additions & 0 deletions src/lib/api-errors.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

const STATUS_MESSAGES: Record<number, string> = {
400: "Invalid request. Please check your input.",
401: "Your session has expired. Please sign in again.",
Expand Down
3 changes: 3 additions & 0 deletions src/lib/auth.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

import { useQueryClient } from "@tanstack/react-query"
import {
createContext,
Expand Down
3 changes: 3 additions & 0 deletions src/lib/feature-flags.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

import { useQuery, useQueryClient } from "@tanstack/react-query"
import { createContext, useContext, useMemo } from "react"
import { api } from "@/api/api"
Expand Down
3 changes: 3 additions & 0 deletions src/lib/logger.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

type LogLevel = "debug" | "info" | "warn" | "error"

const LEVEL_ORDER: Record<LogLevel, number> = {
Expand Down
3 changes: 3 additions & 0 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"

Expand Down
3 changes: 3 additions & 0 deletions src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

import {
MutationCache,
QueryClient,
Expand Down
3 changes: 3 additions & 0 deletions src/pages/home/home-page.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

import { renderWithFileRoutes } from "@/test/renderers"
import { screen } from "@testing-library/react"
import { describe, expect, it } from "vitest"
Expand Down
3 changes: 3 additions & 0 deletions src/pages/home/home-page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

import { Button } from "@/components/ui/button"

export function HomePage() {
Expand Down
3 changes: 3 additions & 0 deletions src/routes/__root.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

import { lazy, Suspense, useEffect } from "react"
import { QueryClient } from "@tanstack/react-query"
import {
Expand All @@ -8,7 +11,7 @@
import { Toaster } from "sonner"
import { useAnalytics } from "@/lib/analytics"

const TanStackRouterDevtools = import.meta.env.PROD

Check warning on line 14 in src/routes/__root.tsx

View workflow job for this annotation

GitHub Actions / lint-and-test

Fast refresh only works when a file only exports components. Move your component(s) to a separate file. If all exports are HOCs, add them to the `extraHOCs` option
? () => null
: lazy(() =>
import("@tanstack/react-router-devtools").then((mod) => ({
Expand All @@ -16,7 +19,7 @@
}))
)

const ReactQueryDevtools = import.meta.env.PROD

Check warning on line 22 in src/routes/__root.tsx

View workflow job for this annotation

GitHub Actions / lint-and-test

Fast refresh only works when a file only exports components. Move your component(s) to a separate file. If all exports are HOCs, add them to the `extraHOCs` option
? () => null
: lazy(() =>
import("@tanstack/react-query-devtools").then((mod) => ({
Expand All @@ -41,7 +44,7 @@
component: RootComponent,
})

function RouteTracker() {

Check warning on line 47 in src/routes/__root.tsx

View workflow job for this annotation

GitHub Actions / lint-and-test

Fast refresh only works when a file only exports components. Move your component(s) to a separate file. If all exports are HOCs, add them to the `extraHOCs` option
const router = useRouter()
const analytics = useAnalytics()
useEffect(() => {
Expand All @@ -52,7 +55,7 @@
return null
}

function RootComponent() {

Check warning on line 58 in src/routes/__root.tsx

View workflow job for this annotation

GitHub Actions / lint-and-test

Fast refresh only works when a file only exports components. Move your component(s) to a separate file. If all exports are HOCs, add them to the `extraHOCs` option
return (
<>
<RouteTracker />
Expand Down
3 changes: 3 additions & 0 deletions src/routes/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

import { createFileRoute } from "@tanstack/react-router"
import { HomePage } from "@/pages/home/home-page"

Expand Down
3 changes: 3 additions & 0 deletions src/test/renderers.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

import { ThemeProvider } from "@/components/theme-provider"
import { AnalyticsProvider } from "@/lib/analytics"
import { AuthProvider } from "@/lib/auth"
Expand Down
3 changes: 3 additions & 0 deletions src/test/setup.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

import { handlers } from "@/api/handlers"
import "@testing-library/jest-dom/vitest"
import { cleanup } from "@testing-library/react"
Expand Down
3 changes: 3 additions & 0 deletions src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

/// <reference types="vite/client" />

interface ImportMetaEnv {
Expand Down
3 changes: 3 additions & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

import path from "path"
import tailwindcss from "@tailwindcss/vite"
import { TanStackRouterVite } from "@tanstack/router-plugin/vite"
Expand Down
3 changes: 3 additions & 0 deletions vitest.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 AG Technology Group LLC
// SPDX-License-Identifier: Apache-2.0

import { defineConfig } from "vitest/config"
import react from "@vitejs/plugin-react"
import path from "path"
Expand Down