@usequark/quark-create-app 1.23.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +210 -0
- package/package.json +56 -0
- package/src/dependabot-config.test.js +128 -0
- package/src/deploy/adapters/iac.js +203 -0
- package/src/deploy/adapters/index.js +27 -0
- package/src/deploy/adapters/railway.js +508 -0
- package/src/deploy/adapters/railway.test.js +318 -0
- package/src/deploy/contract.js +59 -0
- package/src/deploy/deploy.integration.test.js +183 -0
- package/src/deploy/deploy.js +430 -0
- package/src/deploy/deploy.test.js +105 -0
- package/src/deploy/discovery.js +115 -0
- package/src/deploy/discovery.test.js +141 -0
- package/src/deploy/index.js +38 -0
- package/src/deploy/inspect.js +130 -0
- package/src/index.js +2920 -0
- package/src/mobile-template.test.js +232 -0
- package/src/prepare-hooks.test.js +535 -0
- package/src/scaffold-guards.test.js +140 -0
- package/src/scaffold-output.test.js +228 -0
- package/src/template-scripts.test.js +160 -0
- package/src/utils.js +77 -0
- package/src/utils.test.js +95 -0
- package/src/worker-dockerfile.test.js +85 -0
- package/templates/base-project/.cursor/rules/quark.mdc +172 -0
- package/templates/base-project/.dockerignore +14 -0
- package/templates/base-project/.env.railway.example +87 -0
- package/templates/base-project/.github/copilot-instructions.md +65 -0
- package/templates/base-project/.github/dependabot.yml +31 -0
- package/templates/base-project/.github/skills/project-context/SKILL.md +109 -0
- package/templates/base-project/.github/workflows/ci.yml +116 -0
- package/templates/base-project/.github/workflows/dependabot-auto-merge.yml +22 -0
- package/templates/base-project/.github/workflows/install-windows.yml +27 -0
- package/templates/base-project/.github/workflows/release.yml +67 -0
- package/templates/base-project/.railway/railway.ts +40 -0
- package/templates/base-project/CLAUDE.md +163 -0
- package/templates/base-project/MAIN.md +21 -0
- package/templates/base-project/README.md +164 -0
- package/templates/base-project/apps/web/.env.example +99 -0
- package/templates/base-project/apps/web/Dockerfile +38 -0
- package/templates/base-project/apps/web/biome.json +8 -0
- package/templates/base-project/apps/web/jsconfig.json +9 -0
- package/templates/base-project/apps/web/next.config.js +154 -0
- package/templates/base-project/apps/web/package.json +42 -0
- package/templates/base-project/apps/web/postcss.config.mjs +7 -0
- package/templates/base-project/apps/web/public/.well-known/security.txt +3 -0
- package/templates/base-project/apps/web/public/quark.svg +47 -0
- package/templates/base-project/apps/web/scripts/prepare-standalone.mjs +71 -0
- package/templates/base-project/apps/web/src/app/_components/AsciiPanel.js +103 -0
- package/templates/base-project/apps/web/src/app/_components/HealthIndicator.js +85 -0
- package/templates/base-project/apps/web/src/app/_components/HomeThemeToggle.js +63 -0
- package/templates/base-project/apps/web/src/app/_components/QuarkAnimation.js +168 -0
- package/templates/base-project/apps/web/src/app/_components/SyncedPanels.js +212 -0
- package/templates/base-project/apps/web/src/app/_components/UmamiReplayRecorder.js +198 -0
- package/templates/base-project/apps/web/src/app/_components/UmamiWebVitals.js +39 -0
- package/templates/base-project/apps/web/src/app/api/auth/[...nextauth]/route.js +7 -0
- package/templates/base-project/apps/web/src/app/api/auth/apple/route.js +82 -0
- package/templates/base-project/apps/web/src/app/api/auth/google/route.js +59 -0
- package/templates/base-project/apps/web/src/app/api/auth/refresh/route.js +48 -0
- package/templates/base-project/apps/web/src/app/api/auth/register/route.js +63 -0
- package/templates/base-project/apps/web/src/app/api/auth/token/route.js +44 -0
- package/templates/base-project/apps/web/src/app/api/csrf/route.js +40 -0
- package/templates/base-project/apps/web/src/app/api/device/register/route.js +57 -0
- package/templates/base-project/apps/web/src/app/api/error-handler.js +23 -0
- package/templates/base-project/apps/web/src/app/api/files/[id]/route.js +89 -0
- package/templates/base-project/apps/web/src/app/api/files/route.js +120 -0
- package/templates/base-project/apps/web/src/app/api/health/route.js +160 -0
- package/templates/base-project/apps/web/src/app/api/users/[id]/route.js +54 -0
- package/templates/base-project/apps/web/src/app/api/users/me/route.js +67 -0
- package/templates/base-project/apps/web/src/app/api/users/route.js +45 -0
- package/templates/base-project/apps/web/src/app/auth/error/page.js +83 -0
- package/templates/base-project/apps/web/src/app/auth/forgot-password/page.js +58 -0
- package/templates/base-project/apps/web/src/app/auth/provider-loading.js +25 -0
- package/templates/base-project/apps/web/src/app/auth/register/RegisterPageClient.js +340 -0
- package/templates/base-project/apps/web/src/app/auth/register/page.js +17 -0
- package/templates/base-project/apps/web/src/app/auth/signin/SignInPageClient.js +230 -0
- package/templates/base-project/apps/web/src/app/auth/signin/page.js +10 -0
- package/templates/base-project/apps/web/src/app/favicon.ico +0 -0
- package/templates/base-project/apps/web/src/app/global-error.js +58 -0
- package/templates/base-project/apps/web/src/app/globals.css +1166 -0
- package/templates/base-project/apps/web/src/app/icon.svg +47 -0
- package/templates/base-project/apps/web/src/app/layout.js +57 -0
- package/templates/base-project/apps/web/src/app/manifest.js +12 -0
- package/templates/base-project/apps/web/src/app/not-found.js +39 -0
- package/templates/base-project/apps/web/src/app/page.js +262 -0
- package/templates/base-project/apps/web/src/app/robots.js +23 -0
- package/templates/base-project/apps/web/src/app/sitemap.js +14 -0
- package/templates/base-project/apps/web/src/lib/analytics/umami-before-send.js +29 -0
- package/templates/base-project/apps/web/src/lib/analytics/umami-config.js +95 -0
- package/templates/base-project/apps/web/src/lib/analytics/umami-csp.js +24 -0
- package/templates/base-project/apps/web/src/lib/analytics/umami-marketing.js +131 -0
- package/templates/base-project/apps/web/src/lib/analytics/umami-replay.js +204 -0
- package/templates/base-project/apps/web/src/lib/analytics/umami-web-vitals.js +237 -0
- package/templates/base-project/apps/web/src/lib/analytics/umami.js +57 -0
- package/templates/base-project/apps/web/src/lib/auth-middleware.js +82 -0
- package/templates/base-project/apps/web/src/lib/auth-signup.js +5 -0
- package/templates/base-project/apps/web/src/lib/auth.js +191 -0
- package/templates/base-project/apps/web/src/lib/csrf-cookie.js +11 -0
- package/templates/base-project/apps/web/src/lib/jwt.js +84 -0
- package/templates/base-project/apps/web/src/lib/load-crm-config.js +35 -0
- package/templates/base-project/apps/web/src/lib/normalize-auth-request.js +33 -0
- package/templates/base-project/apps/web/src/lib/proxy-auth.js +80 -0
- package/templates/base-project/apps/web/src/lib/seo/indexing.js +25 -0
- package/templates/base-project/apps/web/src/lib/seo/site-metadata.js +74 -0
- package/templates/base-project/apps/web/src/lib/sitemap-entries.js +22 -0
- package/templates/base-project/apps/web/src/preflight.js +55 -0
- package/templates/base-project/apps/web/src/proxy.js +328 -0
- package/templates/base-project/biome.json +52 -0
- package/templates/base-project/docker-compose.override.yml +17 -0
- package/templates/base-project/docker-compose.yml +37 -0
- package/templates/base-project/docs/FIRST_FEATURE.md +74 -0
- package/templates/base-project/docs/START_HERE.md +70 -0
- package/templates/base-project/docs/adr/README.md +42 -0
- package/templates/base-project/package.json +58 -0
- package/templates/base-project/packages/db/package.json +38 -0
- package/templates/base-project/packages/db/prisma/migrations/20260202061128_initial/migration.sql +202 -0
- package/templates/base-project/packages/db/prisma/migrations/migration_lock.toml +3 -0
- package/templates/base-project/packages/db/prisma/schema.prisma +161 -0
- package/templates/base-project/packages/db/prisma/seed.js +152 -0
- package/templates/base-project/packages/db/prisma.config.js +27 -0
- package/templates/base-project/packages/db/src/client.js +75 -0
- package/templates/base-project/packages/db/src/connection.js +44 -0
- package/templates/base-project/packages/db/src/context.js +275 -0
- package/templates/base-project/packages/db/src/index.js +5 -0
- package/templates/base-project/packages/db/src/ping.js +66 -0
- package/templates/base-project/packages/db/src/queries.js +292 -0
- package/templates/base-project/packages/db/src/schemas.js +30 -0
- package/templates/base-project/packages/db/src/umami.js +299 -0
- package/templates/base-project/pnpm-workspace.yaml +20 -0
- package/templates/base-project/scripts/check-loading.mjs +49 -0
- package/templates/base-project/scripts/check-standards.mjs +186 -0
- package/templates/base-project/scripts/clean-workspace.mjs +479 -0
- package/templates/base-project/scripts/dev-preflight.mjs +48 -0
- package/templates/base-project/scripts/doctor.js +408 -0
- package/templates/base-project/scripts/prepare.js +490 -0
- package/templates/base-project/scripts/run-tests.mjs +71 -0
- package/templates/base-project/skills/add-dashboard/SKILL.md +28 -0
- package/templates/base-project/skills/add-endpoint/SKILL.md +29 -0
- package/templates/base-project/skills/add-model/SKILL.md +28 -0
- package/templates/base-project/skills/ecommerce/SKILL.md +224 -0
- package/templates/base-project/skills/ecommerce-cart/SKILL.md +193 -0
- package/templates/base-project/skills/ecommerce-catalog/SKILL.md +179 -0
- package/templates/base-project/skills/ecommerce-checkout/SKILL.md +195 -0
- package/templates/base-project/skills/i18n/SKILL.md +211 -0
- package/templates/base-project/skills/payment/SKILL.md +280 -0
- package/templates/base-project/skills/quark-skills/SKILL.md +37 -0
- package/templates/base-project/turbo.json +143 -0
- package/templates/config/package.json +20 -0
- package/templates/config/src/app-url.js +134 -0
- package/templates/config/src/environment.js +284 -0
- package/templates/config/src/index.js +16 -0
- package/templates/config/src/load-config.js +157 -0
- package/templates/config/src/rate-limit.js +136 -0
- package/templates/config/src/redis.js +43 -0
- package/templates/config/src/validate-env.js +532 -0
- package/templates/jobs/README.md +52 -0
- package/templates/jobs/package.json +9 -0
- package/templates/jobs/src/definitions.js +13 -0
- package/templates/jobs/src/index.js +1 -0
- package/templates/mobile/.env.example +16 -0
- package/templates/mobile/CHECKLIST.md +40 -0
- package/templates/mobile/README.md +130 -0
- package/templates/mobile/app/(app)/_layout.tsx +83 -0
- package/templates/mobile/app/(app)/index.tsx +147 -0
- package/templates/mobile/app/(app)/profile.tsx +204 -0
- package/templates/mobile/app/(app)/settings.tsx +80 -0
- package/templates/mobile/app/(auth)/_layout.tsx +10 -0
- package/templates/mobile/app/(auth)/sign-in.tsx +140 -0
- package/templates/mobile/app/(auth)/sign-up.tsx +178 -0
- package/templates/mobile/app/+not-found.tsx +32 -0
- package/templates/mobile/app/_layout.tsx +18 -0
- package/templates/mobile/app.json +47 -0
- package/templates/mobile/assets/adaptive-icon.png +0 -0
- package/templates/mobile/assets/icon.png +0 -0
- package/templates/mobile/assets/notification-icon.png +0 -0
- package/templates/mobile/assets/splash.png +0 -0
- package/templates/mobile/biome.json +27 -0
- package/templates/mobile/components/AuthGate.tsx +19 -0
- package/templates/mobile/components/ThemeProvider.tsx +23 -0
- package/templates/mobile/eas.json +29 -0
- package/templates/mobile/hooks/use-auth.ts +127 -0
- package/templates/mobile/lib/api-client.ts +138 -0
- package/templates/mobile/lib/api.ts +83 -0
- package/templates/mobile/lib/auth-providers-google.ts +74 -0
- package/templates/mobile/lib/auth-providers.ts +74 -0
- package/templates/mobile/lib/auth.ts +100 -0
- package/templates/mobile/lib/config.ts +14 -0
- package/templates/mobile/lib/errors.ts +46 -0
- package/templates/mobile/lib/notifications.ts +77 -0
- package/templates/mobile/lib/storage.ts +25 -0
- package/templates/mobile/lib/theme-tokens.ts +16 -0
- package/templates/mobile/package.json +37 -0
- package/templates/mobile/tsconfig.json +10 -0
- package/templates/pwa/app/_components/PWARegister.js +15 -0
- package/templates/pwa/app/manifest.js +32 -0
- package/templates/pwa/public/sw.js +72 -0
- package/templates/ui/README.md +44 -0
- package/templates/ui/package.json +21 -0
- package/templates/ui/src/badge.js +23 -0
- package/templates/ui/src/button.js +111 -0
- package/templates/ui/src/card.js +128 -0
- package/templates/ui/src/checkbox.js +20 -0
- package/templates/ui/src/container.js +13 -0
- package/templates/ui/src/dialog.js +137 -0
- package/templates/ui/src/error-banner.js +15 -0
- package/templates/ui/src/footer.js +239 -0
- package/templates/ui/src/form-field.integration-test.js +142 -0
- package/templates/ui/src/form-field.js +50 -0
- package/templates/ui/src/index.js +24 -0
- package/templates/ui/src/input.js +11 -0
- package/templates/ui/src/label.js +14 -0
- package/templates/ui/src/lightbox.integration-test.js +167 -0
- package/templates/ui/src/lightbox.js +243 -0
- package/templates/ui/src/logo.js +52 -0
- package/templates/ui/src/navbar.js +602 -0
- package/templates/ui/src/password-input.js +31 -0
- package/templates/ui/src/rich-text.js +514 -0
- package/templates/ui/src/select.js +317 -0
- package/templates/ui/src/skeleton.js +10 -0
- package/templates/ui/src/spinner.js +31 -0
- package/templates/ui/src/table.js +169 -0
- package/templates/ui/src/textarea.js +11 -0
- package/templates/ui/src/theme-constants.js +24 -0
- package/templates/ui/src/theme.js +196 -0
- package/templates/ui/src/toast.js +206 -0
- package/templates/worker/Dockerfile +64 -0
- package/templates/worker/README.md +56 -0
- package/templates/worker/package.json +36 -0
- package/templates/worker/src/config/pricing.js +46 -0
- package/templates/worker/src/handlers/email-job-validation.js +36 -0
- package/templates/worker/src/handlers/email.js +98 -0
- package/templates/worker/src/handlers/files.js +59 -0
- package/templates/worker/src/handlers/index.js +20 -0
- package/templates/worker/src/handlers/push-notification.js +234 -0
- package/templates/worker/src/index.js +505 -0
- package/templates/worker/src/lib/railway.js +438 -0
- package/templates/worker/src/lib/tokens.js +81 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { hostname, networkInterfaces } from "node:os";
|
|
2
|
+
|
|
3
|
+
import { buildContentSecurityPolicy } from "./src/lib/analytics/umami-csp.js";
|
|
4
|
+
|
|
5
|
+
function getLocalNetworkHosts() {
|
|
6
|
+
const hosts = new Set(["localhost", "127.0.0.1", "0.0.0.0"]);
|
|
7
|
+
|
|
8
|
+
for (const entries of Object.values(networkInterfaces())) {
|
|
9
|
+
for (const entry of entries || []) {
|
|
10
|
+
if (!entry || entry.internal || entry.family !== "IPv4") continue;
|
|
11
|
+
hosts.add(entry.address.split("%")[0]);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const machineHostname = hostname().trim();
|
|
16
|
+
if (machineHostname) {
|
|
17
|
+
hosts.add(machineHostname);
|
|
18
|
+
if (!machineHostname.endsWith(".local")) {
|
|
19
|
+
hosts.add(`${machineHostname}.local`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return [...hosts];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getAllowedDevOrigins() {
|
|
27
|
+
const configuredOrigins = (
|
|
28
|
+
process.env.NEXT_DEV_ALLOWED_ORIGINS ||
|
|
29
|
+
process.env.ALLOWED_DEV_ORIGINS ||
|
|
30
|
+
""
|
|
31
|
+
)
|
|
32
|
+
.split(",")
|
|
33
|
+
.map((origin) => origin.trim())
|
|
34
|
+
.filter(Boolean);
|
|
35
|
+
|
|
36
|
+
return [...new Set([...getLocalNetworkHosts(), ...configuredOrigins])];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const allowedDevOrigins =
|
|
40
|
+
process.env.NODE_ENV === "development" ? getAllowedDevOrigins() : undefined;
|
|
41
|
+
|
|
42
|
+
/** @type {import('next').NextConfig} */
|
|
43
|
+
const nextConfig = {
|
|
44
|
+
// Required for Railway deployment - produces a self-contained build
|
|
45
|
+
// at .next/standalone that can run without node_modules.
|
|
46
|
+
output: "standalone",
|
|
47
|
+
outputFileTracingIncludes: {
|
|
48
|
+
"/*": ["../../packages/db/prisma/schema.prisma"],
|
|
49
|
+
},
|
|
50
|
+
allowedDevOrigins,
|
|
51
|
+
|
|
52
|
+
// Keep BullMQ out of the Turbopack/webpack graph — its ESM build imports
|
|
53
|
+
// Node built-ins (child_process, net, worker_threads, dns, fs).
|
|
54
|
+
serverExternalPackages: ["bullmq"],
|
|
55
|
+
|
|
56
|
+
// Support workspace package resolution (including @techstream/quark-db which uses
|
|
57
|
+
// the Prisma driver-adapter pattern - pure JS, no native engine binary)
|
|
58
|
+
transpilePackages: [
|
|
59
|
+
"@techstream/quark-core",
|
|
60
|
+
"@techstream/quark-db",
|
|
61
|
+
"@techstream/quark-ui",
|
|
62
|
+
"@techstream/quark-jobs",
|
|
63
|
+
],
|
|
64
|
+
|
|
65
|
+
// Security headers
|
|
66
|
+
// NOTE: These are also applied by proxy.js for proxy-matched routes.
|
|
67
|
+
// Keeping them here as a fallback for routes the proxy doesn't match.
|
|
68
|
+
async headers() {
|
|
69
|
+
return [
|
|
70
|
+
{
|
|
71
|
+
source: "/:path*",
|
|
72
|
+
headers: [
|
|
73
|
+
{
|
|
74
|
+
key: "X-DNS-Prefetch-Control",
|
|
75
|
+
value: "on",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
key: "X-Frame-Options",
|
|
79
|
+
value: "SAMEORIGIN",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
key: "X-Content-Type-Options",
|
|
83
|
+
value: "nosniff",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
key: "Referrer-Policy",
|
|
87
|
+
value: "strict-origin-when-cross-origin",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
key: "Permissions-Policy",
|
|
91
|
+
value: "camera=(), microphone=(), geolocation=()",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
key: "Content-Security-Policy",
|
|
95
|
+
// unsafe-eval is required by Turbopack in development only.
|
|
96
|
+
// It is deliberately excluded from the production directive.
|
|
97
|
+
value: buildContentSecurityPolicy(),
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
];
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
// Environment variables validation
|
|
105
|
+
//
|
|
106
|
+
// NEXTAUTH_URL is read by next-auth/react *in the browser*, so it has to be
|
|
107
|
+
// inlined at build time. Leaving it undefined when APP_URL/NEXTAUTH_URL are
|
|
108
|
+
// unset made the client fall back to next-auth's hardcoded
|
|
109
|
+
// http://localhost:3000/api/auth regardless of PORT or host. Derive the same
|
|
110
|
+
// fallback the server uses (packages/config/src/app-url.js) so both sides
|
|
111
|
+
// agree.
|
|
112
|
+
env: {
|
|
113
|
+
APP_URL: process.env.APP_URL,
|
|
114
|
+
NEXTAUTH_URL:
|
|
115
|
+
process.env.NEXTAUTH_URL ||
|
|
116
|
+
process.env.APP_URL ||
|
|
117
|
+
`http://localhost:${process.env.PORT || 3000}`,
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
// Request body size limits (security)
|
|
121
|
+
experimental: {
|
|
122
|
+
// Limit request body size to prevent DoS attacks
|
|
123
|
+
// Default is 4MB, we're being explicit here
|
|
124
|
+
// Adjust based on your needs (e.g., larger for file uploads)
|
|
125
|
+
serverActions: {
|
|
126
|
+
bodySizeLimit: "2mb", // For Server Actions
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
// API route configuration
|
|
131
|
+
async rewrites() {
|
|
132
|
+
return [];
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
// Compiler options for production optimization
|
|
136
|
+
compiler: {
|
|
137
|
+
removeConsole:
|
|
138
|
+
process.env.NODE_ENV === "production"
|
|
139
|
+
? { exclude: ["error", "warn"] }
|
|
140
|
+
: false,
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
// Image optimization configuration
|
|
144
|
+
images: {
|
|
145
|
+
domains: [],
|
|
146
|
+
formats: ["image/avif", "image/webp"],
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
// Production-only settings
|
|
150
|
+
poweredByHeader: false,
|
|
151
|
+
compress: true,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export default nextConfig;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@myquark/web",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"private": true,
|
|
6
|
+
"scripts": {
|
|
7
|
+
"clean": "rm -rf .next",
|
|
8
|
+
"dev": "next dev",
|
|
9
|
+
"build": "next build",
|
|
10
|
+
"prepare:standalone": "node ./scripts/prepare-standalone.mjs",
|
|
11
|
+
"build:deploy": "pnpm build && pnpm prepare:standalone",
|
|
12
|
+
"start": "next start",
|
|
13
|
+
"start:deploy": "node .next/standalone/apps/web/server.js",
|
|
14
|
+
"test": "node ../../scripts/run-tests.mjs src --exclude=integration.test.js",
|
|
15
|
+
"lint": "biome format --write && biome check --write"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@auth/prisma-adapter": "^2.11.3",
|
|
19
|
+
"@aws-sdk/client-s3": "^3.1140.0",
|
|
20
|
+
"@aws-sdk/s3-request-presigner": "^3.1140.0",
|
|
21
|
+
"@prisma/client": "^7.10.0",
|
|
22
|
+
"@techstream/quark-core": "^2.0.0",
|
|
23
|
+
"@techstream/quark-db": "workspace:*",
|
|
24
|
+
"@techstream/quark-jobs": "workspace:*",
|
|
25
|
+
"@techstream/quark-ui": "workspace:*",
|
|
26
|
+
"ioredis": "^6.0.0",
|
|
27
|
+
"jose": "^6.2.12",
|
|
28
|
+
"next": "16.3.6",
|
|
29
|
+
"next-auth": "5.0.0-beta.32",
|
|
30
|
+
"pg": "^8.23.0",
|
|
31
|
+
"react": "19.3.0",
|
|
32
|
+
"react-dom": "19.3.0",
|
|
33
|
+
"rrweb": "2.1.6",
|
|
34
|
+
"zod": "^4.6.5"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@tailwindcss/postcss": "^4.3.3",
|
|
38
|
+
"@types/node": "^26.6.2",
|
|
39
|
+
"tailwindcss": "^4.3.3",
|
|
40
|
+
"@techstream/quark-config": "workspace:*"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" fill="none">
|
|
2
|
+
<title>Quark</title>
|
|
3
|
+
<!--
|
|
4
|
+
Quark logo - SVG master
|
|
5
|
+
Geometry mirrors QuarkAnimation.js constants (ring/dash thickness ratio ~0.20)
|
|
6
|
+
|
|
7
|
+
Ring: center (100,100), centerline radius 65, stroke-width 26
|
|
8
|
+
Gap: centred at 45.8° (dashAngle=0.8 rad), half-width 27.1° (0.472 rad)
|
|
9
|
+
Dash: radial at 45.8°, from radius 23.4 → 103.4 (proportional to animation dL)
|
|
10
|
+
stroke-width 28 (= dashThickness/ringThickness × 26 = 2.8/2.6 × 26)
|
|
11
|
+
|
|
12
|
+
Colors: #2d3436 (dark/top) · #377dff (blue/bottom-left) · #ff4757 (red/tail)
|
|
13
|
+
-->
|
|
14
|
+
|
|
15
|
+
<!-- Dark arc - upper semicircle: left (180°) → top (270°) → right (360°) -->
|
|
16
|
+
<path
|
|
17
|
+
d="M 35,100 A 65,65 0 0,1 165,100"
|
|
18
|
+
stroke="#2d3436"
|
|
19
|
+
stroke-width="26"
|
|
20
|
+
stroke-linecap="butt"
|
|
21
|
+
/>
|
|
22
|
+
|
|
23
|
+
<!-- Blue arc - bottom-left: gap-end (72.9°) → left (180°) -->
|
|
24
|
+
<path
|
|
25
|
+
d="M 119.1,162.1 A 65,65 0 0,1 35,100"
|
|
26
|
+
stroke="#377dff"
|
|
27
|
+
stroke-width="26"
|
|
28
|
+
stroke-linecap="butt"
|
|
29
|
+
/>
|
|
30
|
+
|
|
31
|
+
<!-- Red arc - tiny pre-gap sliver: right (0°) → gap-start (18.8°) -->
|
|
32
|
+
<path
|
|
33
|
+
d="M 165,100 A 65,65 0 0,1 161.5,121"
|
|
34
|
+
stroke="#ff4757"
|
|
35
|
+
stroke-width="26"
|
|
36
|
+
stroke-linecap="butt"
|
|
37
|
+
/>
|
|
38
|
+
|
|
39
|
+
<!-- Red dash - radial tail through the gap -->
|
|
40
|
+
<line
|
|
41
|
+
x1="116.3" y1="116.8"
|
|
42
|
+
x2="172" y2="174.2"
|
|
43
|
+
stroke="#ff4757"
|
|
44
|
+
stroke-width="28"
|
|
45
|
+
stroke-linecap="butt"
|
|
46
|
+
/>
|
|
47
|
+
</svg>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { access, cp, mkdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const appRoot = path.resolve(scriptDir, "..");
|
|
7
|
+
const standaloneRoot = path.join(appRoot, ".next", "standalone", "apps", "web");
|
|
8
|
+
|
|
9
|
+
async function assertExists(targetPath, description) {
|
|
10
|
+
try {
|
|
11
|
+
await access(targetPath);
|
|
12
|
+
} catch {
|
|
13
|
+
throw new Error(
|
|
14
|
+
`${description} was not found at ${targetPath}. Run "pnpm build" in apps/web before preparing the standalone artifact.`,
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function copyIntoStandalone(sourcePath, destinationPath) {
|
|
20
|
+
await mkdir(path.dirname(destinationPath), { recursive: true });
|
|
21
|
+
await cp(sourcePath, destinationPath, { recursive: true, force: true });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function main() {
|
|
25
|
+
await assertExists(
|
|
26
|
+
path.join(standaloneRoot, "server.js"),
|
|
27
|
+
"The standalone server output",
|
|
28
|
+
);
|
|
29
|
+
await assertExists(
|
|
30
|
+
path.join(appRoot, "public"),
|
|
31
|
+
"The public asset directory",
|
|
32
|
+
);
|
|
33
|
+
await assertExists(
|
|
34
|
+
path.join(appRoot, ".next", "static"),
|
|
35
|
+
"The Next static asset directory",
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
await copyIntoStandalone(
|
|
39
|
+
path.join(appRoot, "public"),
|
|
40
|
+
path.join(standaloneRoot, "public"),
|
|
41
|
+
);
|
|
42
|
+
await copyIntoStandalone(
|
|
43
|
+
path.join(appRoot, ".next", "static"),
|
|
44
|
+
path.join(standaloneRoot, ".next", "static"),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Copy Prisma schema so admin introspect resolves it from standalone cwd
|
|
48
|
+
// scriptDir = apps/web/scripts → ../../../ = repo root
|
|
49
|
+
const repoRoot = path.resolve(scriptDir, "../../..");
|
|
50
|
+
const schemaSource = path.join(
|
|
51
|
+
repoRoot,
|
|
52
|
+
"packages",
|
|
53
|
+
"db",
|
|
54
|
+
"prisma",
|
|
55
|
+
"schema.prisma",
|
|
56
|
+
);
|
|
57
|
+
const schemaDest = path.join(standaloneRoot, "schema.prisma");
|
|
58
|
+
try {
|
|
59
|
+
await access(schemaSource);
|
|
60
|
+
await copyIntoStandalone(schemaSource, schemaDest);
|
|
61
|
+
} catch {
|
|
62
|
+
console.warn(
|
|
63
|
+
`Warning: Prisma schema not found at ${schemaSource}; skipping copy into standalone.`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
main().catch((error) => {
|
|
69
|
+
console.error(error.message);
|
|
70
|
+
process.exitCode = 1;
|
|
71
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
const INNER_W = 34;
|
|
4
|
+
|
|
5
|
+
function wrapText(text, maxLen) {
|
|
6
|
+
const words = text.split(" ");
|
|
7
|
+
const lines = [];
|
|
8
|
+
let current = "";
|
|
9
|
+
for (const word of words) {
|
|
10
|
+
if (current.length + (current ? 1 : 0) + word.length > maxLen) {
|
|
11
|
+
if (current) lines.push(current);
|
|
12
|
+
current = word.length > maxLen ? word.slice(0, maxLen) : word;
|
|
13
|
+
} else {
|
|
14
|
+
current = current ? `${current} ${word}` : word;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
if (current) lines.push(current);
|
|
18
|
+
return lines.length ? lines : [""];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function border(top) {
|
|
22
|
+
const inner = "\u2500".repeat(INNER_W + 2);
|
|
23
|
+
return top ? `\u250C${inner}\u2510` : `\u2514${inner}\u2518`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function emptyLine() {
|
|
27
|
+
return `\u2502${" ".repeat(INNER_W + 2)}\u2502`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default function AsciiPanel({
|
|
31
|
+
title,
|
|
32
|
+
description,
|
|
33
|
+
prompt,
|
|
34
|
+
charCount,
|
|
35
|
+
onCopy,
|
|
36
|
+
copied,
|
|
37
|
+
maxLines,
|
|
38
|
+
}) {
|
|
39
|
+
const titleChars = wrapText(title, INNER_W);
|
|
40
|
+
const descChars = wrapText(description, INNER_W);
|
|
41
|
+
|
|
42
|
+
const contentLines = [...titleChars, "", ...descChars];
|
|
43
|
+
const totalDisplayLines = Math.max(maxLines, contentLines.length);
|
|
44
|
+
|
|
45
|
+
function getChar(lineIdx, charIdx) {
|
|
46
|
+
let offset = 0;
|
|
47
|
+
for (let i = 0; i < lineIdx; i++) {
|
|
48
|
+
offset += contentLines[i].length;
|
|
49
|
+
}
|
|
50
|
+
const pos = offset + charIdx;
|
|
51
|
+
if (pos < charCount) {
|
|
52
|
+
return contentLines[lineIdx]?.[charIdx] ?? " ";
|
|
53
|
+
}
|
|
54
|
+
return " ";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const rows = [];
|
|
58
|
+
for (let li = 0; li < totalDisplayLines; li++) {
|
|
59
|
+
const line = contentLines[li] ?? "";
|
|
60
|
+
const isBlank = li >= contentLines.length || line === "";
|
|
61
|
+
if (isBlank) {
|
|
62
|
+
rows.push(
|
|
63
|
+
<span key={li} className="quark-ascii-line">
|
|
64
|
+
{`${emptyLine()}\n`}
|
|
65
|
+
</span>,
|
|
66
|
+
);
|
|
67
|
+
} else {
|
|
68
|
+
const chars = [];
|
|
69
|
+
for (let ci = 0; ci < INNER_W; ci++) {
|
|
70
|
+
chars.push(
|
|
71
|
+
<span key={ci} className="quark-ascii-char">
|
|
72
|
+
{getChar(li, ci)}
|
|
73
|
+
</span>,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
rows.push(
|
|
77
|
+
<span key={li} className="quark-ascii-line">
|
|
78
|
+
{`\u2502 `}
|
|
79
|
+
{chars}
|
|
80
|
+
{` \u2502\n`}
|
|
81
|
+
</span>,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<article className="quark-ascii-panel" title={prompt}>
|
|
88
|
+
<pre className="quark-ascii-box">
|
|
89
|
+
{`${border(true)}\n`}
|
|
90
|
+
{rows}
|
|
91
|
+
{`${border(false)}`}
|
|
92
|
+
</pre>
|
|
93
|
+
<button
|
|
94
|
+
type="button"
|
|
95
|
+
className={`quark-ascii-copy${copied ? " quark-ascii-copy--copied" : ""}`}
|
|
96
|
+
onClick={onCopy}
|
|
97
|
+
aria-label={copied ? "Copied" : "Copy prompt"}
|
|
98
|
+
>
|
|
99
|
+
{copied ? "copied" : "copy"}
|
|
100
|
+
</button>
|
|
101
|
+
</article>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
|
|
5
|
+
const STATUS_COLORS = {
|
|
6
|
+
ok: "#22c55e",
|
|
7
|
+
degraded: "#f59e0b",
|
|
8
|
+
error: "#ef4444",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
function Dot({ status, label }) {
|
|
12
|
+
const dotColor = STATUS_COLORS[status] ?? "var(--quark-health-dot-idle)";
|
|
13
|
+
return (
|
|
14
|
+
<span
|
|
15
|
+
title={`${label}: ${status ?? "checking…"}`}
|
|
16
|
+
style={{
|
|
17
|
+
display: "inline-flex",
|
|
18
|
+
alignItems: "center",
|
|
19
|
+
gap: "4px",
|
|
20
|
+
color: "var(--quark-health-text)",
|
|
21
|
+
fontFamily: "monospace",
|
|
22
|
+
fontSize: "11px",
|
|
23
|
+
}}
|
|
24
|
+
>
|
|
25
|
+
<span
|
|
26
|
+
style={{
|
|
27
|
+
display: "inline-block",
|
|
28
|
+
width: "5px",
|
|
29
|
+
height: "5px",
|
|
30
|
+
borderRadius: "50%",
|
|
31
|
+
backgroundColor: dotColor,
|
|
32
|
+
transition: "background-color 0.3s ease",
|
|
33
|
+
flexShrink: 0,
|
|
34
|
+
}}
|
|
35
|
+
/>
|
|
36
|
+
{label}
|
|
37
|
+
</span>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Fetches /api/health once on mount and renders small coloured dots for
|
|
43
|
+
* database, Redis, and storage status. Shows neutral dots while loading.
|
|
44
|
+
* Fails silently so the home page aesthetic is never disrupted.
|
|
45
|
+
* Colours are driven by CSS custom properties (prefers-color-scheme aware).
|
|
46
|
+
*/
|
|
47
|
+
export default function HealthIndicator() {
|
|
48
|
+
const [checks, setChecks] = useState(null);
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
let cancelled = false;
|
|
52
|
+
fetch("/api/health")
|
|
53
|
+
.then((r) => r.json())
|
|
54
|
+
.then((data) => {
|
|
55
|
+
if (!cancelled) setChecks(data.checks ?? {});
|
|
56
|
+
})
|
|
57
|
+
.catch(() => {
|
|
58
|
+
if (!cancelled) setChecks({});
|
|
59
|
+
});
|
|
60
|
+
return () => {
|
|
61
|
+
cancelled = true;
|
|
62
|
+
};
|
|
63
|
+
}, []);
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div
|
|
67
|
+
style={{
|
|
68
|
+
display: "flex",
|
|
69
|
+
alignItems: "center",
|
|
70
|
+
gap: "8px",
|
|
71
|
+
fontFamily: "monospace",
|
|
72
|
+
fontSize: "11px",
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
<Dot status={checks?.database?.status} label="db" />
|
|
76
|
+
<span style={{ color: "var(--quark-health-sep)" }}>·</span>
|
|
77
|
+
<Dot status={checks?.redis?.status} label="redis" />
|
|
78
|
+
<span style={{ color: "var(--quark-health-sep)" }}>·</span>
|
|
79
|
+
<Dot
|
|
80
|
+
status={checks?.storage?.status}
|
|
81
|
+
label={checks?.storage?.provider ?? "storage"}
|
|
82
|
+
/>
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
THEME_ATTR,
|
|
5
|
+
THEME_CHANGE_EVENT,
|
|
6
|
+
THEME_STORAGE_KEY,
|
|
7
|
+
} from "@techstream/quark-ui/theme-constants";
|
|
8
|
+
import { useLayoutEffect, useState } from "react";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Text-link theme toggle for the home page nav.
|
|
12
|
+
* Shows "light" when dark mode is active (click to switch to light) and vice versa.
|
|
13
|
+
* Styled identically to the other nav links via the quark-home-link class.
|
|
14
|
+
*
|
|
15
|
+
* Uses the same localStorage key as ThemeProvider (THEME_STORAGE_KEY) and sets
|
|
16
|
+
* the same HTML attribute (THEME_ATTR) so CSS variables react instantly. Fires
|
|
17
|
+
* a THEME_CHANGE_EVENT custom event so ThemeProvider (if present) stays in sync
|
|
18
|
+
* without any import coupling between the two packages.
|
|
19
|
+
*/
|
|
20
|
+
export default function HomeThemeToggle() {
|
|
21
|
+
const [isDark, setIsDark] = useState(true);
|
|
22
|
+
|
|
23
|
+
useLayoutEffect(() => {
|
|
24
|
+
const stored = localStorage.getItem(THEME_STORAGE_KEY);
|
|
25
|
+
if (stored === "light" || stored === "dark") {
|
|
26
|
+
setIsDark(stored === "dark");
|
|
27
|
+
document.documentElement.setAttribute(THEME_ATTR, stored);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const osDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
31
|
+
setIsDark(osDark);
|
|
32
|
+
}, []);
|
|
33
|
+
|
|
34
|
+
function toggle() {
|
|
35
|
+
setIsDark((prev) => {
|
|
36
|
+
const next = prev ? "light" : "dark";
|
|
37
|
+
localStorage.setItem(THEME_STORAGE_KEY, next);
|
|
38
|
+
document.documentElement.setAttribute(THEME_ATTR, next);
|
|
39
|
+
// Notify ThemeProvider (if present in the tree) so React context stays in sync.
|
|
40
|
+
document.dispatchEvent(
|
|
41
|
+
new CustomEvent(THEME_CHANGE_EVENT, { detail: { theme: next } }),
|
|
42
|
+
);
|
|
43
|
+
return !prev;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<button
|
|
49
|
+
type="button"
|
|
50
|
+
onClick={toggle}
|
|
51
|
+
aria-label={`Switch to ${isDark ? "light" : "dark"} mode`}
|
|
52
|
+
className="quark-home-link"
|
|
53
|
+
style={{
|
|
54
|
+
background: "none",
|
|
55
|
+
border: "none",
|
|
56
|
+
padding: 0,
|
|
57
|
+
cursor: "pointer",
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
{isDark ? "light" : "dark"}
|
|
61
|
+
</button>
|
|
62
|
+
);
|
|
63
|
+
}
|