@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,408 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* scripts/doctor.js
|
|
4
|
+
*
|
|
5
|
+
* Audits this project for unfinished post-scaffold customisation.
|
|
6
|
+
* Run it at any time - it is safe, read-only by default.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* pnpm doctor # Audit only
|
|
10
|
+
* pnpm doctor:fix # Audit + auto-remove Quark aesthetic scaffolding
|
|
11
|
+
* pnpm doctor:ci # CI-safe checks only (no .env, exits 1 on errors)
|
|
12
|
+
*
|
|
13
|
+
* Extend the CHECKS array below to add your own project-specific rules.
|
|
14
|
+
* This script has zero external dependencies.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from "node:fs";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
20
|
+
|
|
21
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const ROOT = path.resolve(__dirname, "..");
|
|
23
|
+
const FIX = process.argv.includes("--fix");
|
|
24
|
+
const CI = process.argv.includes("--ci");
|
|
25
|
+
|
|
26
|
+
// ─── ANSI helpers ─────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
const c = {
|
|
29
|
+
reset: "\x1b[0m",
|
|
30
|
+
bold: "\x1b[1m",
|
|
31
|
+
dim: "\x1b[2m",
|
|
32
|
+
red: "\x1b[31m",
|
|
33
|
+
green: "\x1b[32m",
|
|
34
|
+
yellow: "\x1b[33m",
|
|
35
|
+
blue: "\x1b[34m",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const fmt = {
|
|
39
|
+
bold: (s) => `${c.bold}${s}${c.reset}`,
|
|
40
|
+
dim: (s) => `${c.dim}${s}${c.reset}`,
|
|
41
|
+
red: (s) => `${c.red}${s}${c.reset}`,
|
|
42
|
+
green: (s) => `${c.green}${s}${c.reset}`,
|
|
43
|
+
yellow: (s) => `${c.yellow}${s}${c.reset}`,
|
|
44
|
+
blue: (s) => `${c.blue}${s}${c.reset}`,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
function exists(rel) {
|
|
50
|
+
return fs.existsSync(path.join(ROOT, rel));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function read(rel) {
|
|
54
|
+
const abs = path.join(ROOT, rel);
|
|
55
|
+
return fs.existsSync(abs) ? fs.readFileSync(abs, "utf-8") : null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Remove a file or directory tree (relative to ROOT). */
|
|
59
|
+
function remove(rel) {
|
|
60
|
+
const abs = path.join(ROOT, rel);
|
|
61
|
+
fs.rmSync(abs, { recursive: true, force: true });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Write content to a file (relative to ROOT), creating parent dirs as needed. */
|
|
65
|
+
function write(rel, content) {
|
|
66
|
+
const abs = path.join(ROOT, rel);
|
|
67
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
68
|
+
fs.writeFileSync(abs, content, "utf-8");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Recursively find source files under `dir` that contain `importString`.
|
|
73
|
+
* Returns an array of absolute file paths.
|
|
74
|
+
*/
|
|
75
|
+
function findImports(dir, importString) {
|
|
76
|
+
const results = [];
|
|
77
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
78
|
+
for (const entry of entries) {
|
|
79
|
+
const full = path.join(dir, entry.name);
|
|
80
|
+
if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== ".next") {
|
|
81
|
+
results.push(...findImports(full, importString));
|
|
82
|
+
} else if (entry.isFile() && /\.(js|jsx|ts|tsx|mjs)$/.test(entry.name)) {
|
|
83
|
+
const content = fs.readFileSync(full, "utf-8");
|
|
84
|
+
if (content.includes(importString)) {
|
|
85
|
+
results.push(full);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return results;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ─── Finding model ────────────────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @typedef {{ category: string, status: 'error'|'warn'|'info', key: string,
|
|
96
|
+
* message: string, detail: string, fix: string, fixable: boolean }} Finding
|
|
97
|
+
*/
|
|
98
|
+
|
|
99
|
+
/** @type {Finding[]} */
|
|
100
|
+
const findings = [];
|
|
101
|
+
|
|
102
|
+
function warn(key, category, message, detail, fix, fixable = false) {
|
|
103
|
+
findings.push({ category, status: "warn", key, message, detail, fix, fixable });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function error(key, category, message, detail, fix, fixable = false) {
|
|
107
|
+
findings.push({ category, status: "error", key, message, detail, fix, fixable });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function info(key, category, message, detail, fix) {
|
|
111
|
+
findings.push({ category, status: "info", key, message, detail, fix, fixable: false });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ─── Checks ───────────────────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
// Read .quark-link.json to understand what packages were included at scaffold time.
|
|
117
|
+
const quarkLink = (() => {
|
|
118
|
+
try {
|
|
119
|
+
return JSON.parse(read(".quark-link.json") ?? "{}");
|
|
120
|
+
} catch {
|
|
121
|
+
return {};
|
|
122
|
+
}
|
|
123
|
+
})();
|
|
124
|
+
const hasUI = Array.isArray(quarkLink.packages) && quarkLink.packages.includes("ui");
|
|
125
|
+
const installedPackages = Array.isArray(quarkLink.packages) ? quarkLink.packages : [];
|
|
126
|
+
|
|
127
|
+
// ── Structural checks (always run - safe in CI without .env) ─────────────────
|
|
128
|
+
|
|
129
|
+
// ── Check S1: app/page.js conflicts with a route group ────────────────────────
|
|
130
|
+
const rootPagePath = "apps/web/src/app/page.js";
|
|
131
|
+
const appDir = path.join(ROOT, "apps/web/src/app");
|
|
132
|
+
if (exists(rootPagePath) && fs.existsSync(appDir)) {
|
|
133
|
+
const routeGroups = fs
|
|
134
|
+
.readdirSync(appDir, { withFileTypes: true })
|
|
135
|
+
.filter((e) => e.isDirectory() && /^\(.+\)$/.test(e.name))
|
|
136
|
+
.map((e) => ` apps/web/src/app/${e.name}/`);
|
|
137
|
+
|
|
138
|
+
if (routeGroups.length > 0) {
|
|
139
|
+
error(
|
|
140
|
+
"page-route-conflict",
|
|
141
|
+
"routing",
|
|
142
|
+
"apps/web/src/app/page.js conflicts with a route group - Next.js will fail to build",
|
|
143
|
+
routeGroups.join("\n"),
|
|
144
|
+
"Delete apps/web/src/app/page.js and move its content into the route group's page.js",
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ── Check S2: Forgot-password page is a placeholder stub ──────────────────────
|
|
150
|
+
const forgotPwContent = read("apps/web/src/app/auth/forgot-password/page.js");
|
|
151
|
+
if (forgotPwContent && forgotPwContent.includes("quark-auth-layout")) {
|
|
152
|
+
warn(
|
|
153
|
+
"forgot-password-stub",
|
|
154
|
+
"security",
|
|
155
|
+
"Forgot-password page is a scaffold stub - users cannot reset their password",
|
|
156
|
+
"apps/web/src/app/auth/forgot-password/page.js",
|
|
157
|
+
"Implement a password reset flow (email token) before going to production, or remove this route",
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ── Check S3: Source files import packages not installed ───────────────────────
|
|
162
|
+
{
|
|
163
|
+
const optionalPackages = ["ui", "jobs"];
|
|
164
|
+
const scope = (() => {
|
|
165
|
+
try {
|
|
166
|
+
const pkg = JSON.parse(read("package.json") ?? "{}");
|
|
167
|
+
const m = pkg.name?.match(/^@([^/]+)\//);
|
|
168
|
+
return m ? m[1] : null;
|
|
169
|
+
} catch {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
})();
|
|
173
|
+
|
|
174
|
+
if (scope) {
|
|
175
|
+
const missingPkgs = optionalPackages.filter(
|
|
176
|
+
(p) => !installedPackages.includes(p),
|
|
177
|
+
);
|
|
178
|
+
for (const pkg of missingPkgs) {
|
|
179
|
+
const importPattern = `@${scope}/${pkg}`;
|
|
180
|
+
const srcDir = path.join(ROOT, "apps/web/src");
|
|
181
|
+
if (fs.existsSync(srcDir)) {
|
|
182
|
+
const badFiles = findImports(srcDir, importPattern);
|
|
183
|
+
if (badFiles.length > 0) {
|
|
184
|
+
error(
|
|
185
|
+
`orphan-import-${pkg}`,
|
|
186
|
+
"imports",
|
|
187
|
+
`Source files import "${importPattern}" but "${pkg}" is not installed`,
|
|
188
|
+
badFiles.map((f) => ` ${path.relative(ROOT, f)}`).join("\n"),
|
|
189
|
+
`Remove these imports or run: npx @techstream/quark-create-app add ${pkg}`,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ── Check S4: .quark-link.json missing ────────────────────────────────────────
|
|
198
|
+
if (!exists(".quark-link.json")) {
|
|
199
|
+
warn(
|
|
200
|
+
"quark-link-missing",
|
|
201
|
+
"configuration",
|
|
202
|
+
".quark-link.json is missing - Quark CLI commands (update, add) will not work",
|
|
203
|
+
"",
|
|
204
|
+
"If this is a Quark project, restore .quark-link.json from git history",
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ── Check S5: .env.example contains placeholder scaffold values ───────────────
|
|
209
|
+
const exampleContent = read(".env.example");
|
|
210
|
+
if (exampleContent) {
|
|
211
|
+
const descLine = exampleContent.match(/^APP_DESCRIPTION=(.+)$/m);
|
|
212
|
+
const descVal = descLine?.[1]?.trim() ?? "";
|
|
213
|
+
if (/\bapplication\b$/i.test(descVal) && descVal.split(" ").length <= 3) {
|
|
214
|
+
warn(
|
|
215
|
+
"generic-description",
|
|
216
|
+
"metadata",
|
|
217
|
+
"APP_DESCRIPTION in .env.example is still a generic scaffold value",
|
|
218
|
+
`.env.example → APP_DESCRIPTION="${descVal}"`,
|
|
219
|
+
"Update APP_DESCRIPTION in .env.example to describe your actual project",
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ── Environment-dependent checks (skipped in CI mode) ─────────────────────────
|
|
225
|
+
|
|
226
|
+
if (!CI) {
|
|
227
|
+
|
|
228
|
+
// ── Check E1: Quark home scaffold still present ───────────────────────────────
|
|
229
|
+
const homeScaffoldFiles = [
|
|
230
|
+
"apps/web/src/app/_components/QuarkAnimation.js",
|
|
231
|
+
"apps/web/src/app/_components/HealthIndicator.js",
|
|
232
|
+
"apps/web/src/app/_components/HomeThemeToggle.js",
|
|
233
|
+
].filter(exists);
|
|
234
|
+
|
|
235
|
+
if (homeScaffoldFiles.length > 0) {
|
|
236
|
+
warn(
|
|
237
|
+
"quark-home-scaffold",
|
|
238
|
+
"branding",
|
|
239
|
+
"Quark home scaffold components are still present",
|
|
240
|
+
homeScaffoldFiles.join("\n"),
|
|
241
|
+
"Replace these with your own hero content and update apps/web/src/app/page.js",
|
|
242
|
+
true,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ── Check E2: Quark layout scaffold still present ─────────────────────────────
|
|
247
|
+
if (exists("apps/web/src/app/layout/_components/FloatingThemeToggle.js")) {
|
|
248
|
+
warn(
|
|
249
|
+
"quark-layout-scaffold",
|
|
250
|
+
"branding",
|
|
251
|
+
"Quark layout scaffold component is still present (FloatingThemeToggle)",
|
|
252
|
+
"apps/web/src/app/layout/_components/FloatingThemeToggle.js",
|
|
253
|
+
"Replace or remove the floating theme toggle and update the root layout with your own design",
|
|
254
|
+
true,
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ── Check E4: APP_NAME / APP_DESCRIPTION still reference "Quark" ──────────────
|
|
259
|
+
const envContent = read(".env");
|
|
260
|
+
if (envContent) {
|
|
261
|
+
const nameMatch = envContent.match(/^APP_NAME=(.+)$/m);
|
|
262
|
+
const descMatch = envContent.match(/^APP_DESCRIPTION=(.+)$/m);
|
|
263
|
+
const nameVal = nameMatch?.[1]?.trim() ?? "";
|
|
264
|
+
const descVal = descMatch?.[1]?.trim() ?? "";
|
|
265
|
+
|
|
266
|
+
if (/\bquark\b/i.test(nameVal) || /\bquark\b/i.test(descVal)) {
|
|
267
|
+
warn(
|
|
268
|
+
"app-identity",
|
|
269
|
+
"metadata",
|
|
270
|
+
'APP_NAME or APP_DESCRIPTION still references "Quark"',
|
|
271
|
+
`.env → APP_NAME="${nameVal}", APP_DESCRIPTION="${descVal}"`,
|
|
272
|
+
"Update APP_NAME and APP_DESCRIPTION in your .env to match your project",
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ── Check E5: Placeholder secrets (CHANGE_ME) left in .env ───────────────────
|
|
278
|
+
if (envContent && envContent.includes("CHANGE_ME")) {
|
|
279
|
+
const lines = envContent
|
|
280
|
+
.split("\n")
|
|
281
|
+
.map((l, i) => ({ line: i + 1, text: l }))
|
|
282
|
+
.filter(({ text }) => text.includes("CHANGE_ME"))
|
|
283
|
+
.map(({ line, text }) => ` line ${line}: ${text.split("=")[0]}`);
|
|
284
|
+
|
|
285
|
+
error(
|
|
286
|
+
"secrets",
|
|
287
|
+
"security",
|
|
288
|
+
"CHANGE_ME placeholders found in .env - rotate these before deploying",
|
|
289
|
+
lines.join("\n"),
|
|
290
|
+
"Replace every CHANGE_ME value with a real secret",
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ── Check E6: .env missing vars that exist in .env.example ───────────────────
|
|
295
|
+
if (envContent && exampleContent) {
|
|
296
|
+
const defined = new Set(
|
|
297
|
+
envContent
|
|
298
|
+
.split("\n")
|
|
299
|
+
.filter((l) => l.includes("=") && !l.startsWith("#"))
|
|
300
|
+
.map((l) => l.split("=")[0].trim()),
|
|
301
|
+
);
|
|
302
|
+
const missing = exampleContent
|
|
303
|
+
.split("\n")
|
|
304
|
+
.filter((l) => l.includes("=") && !l.startsWith("#"))
|
|
305
|
+
.map((l) => l.split("=")[0].trim())
|
|
306
|
+
.filter((k) => k && !defined.has(k));
|
|
307
|
+
|
|
308
|
+
if (missing.length > 0) {
|
|
309
|
+
warn(
|
|
310
|
+
"env-missing",
|
|
311
|
+
"configuration",
|
|
312
|
+
`.env.example defines ${missing.length} key(s) not present in .env`,
|
|
313
|
+
missing.map((k) => ` ${k}`).join("\n"),
|
|
314
|
+
"Add the missing keys to your .env (copy from .env.example and fill in values)",
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ── Check E7: README still contains Quark template content ───────────────────
|
|
320
|
+
const readmeContent = read("README.md");
|
|
321
|
+
if (readmeContent && /quark/i.test(readmeContent)) {
|
|
322
|
+
info(
|
|
323
|
+
"readme",
|
|
324
|
+
"documentation",
|
|
325
|
+
"README.md still contains references to Quark",
|
|
326
|
+
"README.md",
|
|
327
|
+
"Update the README to describe your own project",
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
} // end if (!CI)
|
|
332
|
+
|
|
333
|
+
// ─── --fix: auto-remove fixable items ────────────────────────────────────────
|
|
334
|
+
|
|
335
|
+
const STATUS_ICON = { error: "✗", warn: "⚠", info: "·" };
|
|
336
|
+
const STATUS_COLOR = { error: fmt.red, warn: fmt.yellow, info: fmt.dim };
|
|
337
|
+
|
|
338
|
+
// Print the report first so users see what was found before anything is changed.
|
|
339
|
+
console.log(fmt.bold(fmt.blue(`\n🩺 Quark Doctor${CI ? " (CI)" : ""}\n`)));
|
|
340
|
+
|
|
341
|
+
if (findings.length === 0) {
|
|
342
|
+
console.log(fmt.green(" ✓ Nothing to do - project looks clean!\n"));
|
|
343
|
+
process.exit(0);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Group by category
|
|
347
|
+
const categories = [...new Set(findings.map((f) => f.category))];
|
|
348
|
+
for (const cat of categories) {
|
|
349
|
+
console.log(fmt.bold(` ${cat}`));
|
|
350
|
+
for (const f of findings.filter((f) => f.category === cat)) {
|
|
351
|
+
const icon = STATUS_ICON[f.status];
|
|
352
|
+
const colorFn = STATUS_COLOR[f.status];
|
|
353
|
+
console.log(` ${colorFn(`${icon} ${f.message}`)}`);
|
|
354
|
+
if (f.detail) {
|
|
355
|
+
for (const line of f.detail.split("\n")) {
|
|
356
|
+
console.log(fmt.dim(` ${line}`));
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (!FIX || !f.fixable) {
|
|
360
|
+
console.log(fmt.dim(` → ${f.fix}`));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
console.log();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const errorCount = findings.filter((f) => f.status === "error").length;
|
|
367
|
+
const warnCount = findings.filter((f) => f.status === "warn").length;
|
|
368
|
+
const fixableCount = findings.filter((f) => f.fixable).length;
|
|
369
|
+
|
|
370
|
+
const parts = [];
|
|
371
|
+
if (errorCount) parts.push(fmt.red(`${errorCount} error${errorCount > 1 ? "s" : ""}`));
|
|
372
|
+
if (warnCount) parts.push(fmt.yellow(`${warnCount} warning${warnCount > 1 ? "s" : ""}`));
|
|
373
|
+
console.log(fmt.bold(` Summary: ${parts.join(", ")}`));
|
|
374
|
+
|
|
375
|
+
if (FIX && !CI) {
|
|
376
|
+
const fixable = findings.filter((f) => f.fixable);
|
|
377
|
+
if (fixable.length === 0) {
|
|
378
|
+
console.log(fmt.dim(" No auto-fixable items found.\n"));
|
|
379
|
+
} else {
|
|
380
|
+
console.log(fmt.bold(fmt.blue("\n🔧 Applying fixes…\n")));
|
|
381
|
+
for (const f of fixable) {
|
|
382
|
+
if (f.key === "quark-home-scaffold") {
|
|
383
|
+
for (const file of homeScaffoldFiles) {
|
|
384
|
+
remove(file);
|
|
385
|
+
console.log(fmt.green(` ✓ Removed ${file}`));
|
|
386
|
+
}
|
|
387
|
+
// Replace page.js with a minimal stub so removed imports no longer crash the build
|
|
388
|
+
write(
|
|
389
|
+
"apps/web/src/app/page.js",
|
|
390
|
+
`export default function Home() {\n\treturn <main>Hello world</main>;\n}\n`,
|
|
391
|
+
);
|
|
392
|
+
console.log(fmt.green(" ✓ Replaced apps/web/src/app/page.js with a minimal stub"));
|
|
393
|
+
} else if (f.key === "quark-layout-scaffold") {
|
|
394
|
+
remove("apps/web/src/app/layout/_components");
|
|
395
|
+
console.log(fmt.green(" ✓ Removed apps/web/src/app/layout/_components/"));
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
console.log();
|
|
399
|
+
}
|
|
400
|
+
} else if (fixableCount > 0) {
|
|
401
|
+
console.log(
|
|
402
|
+
fmt.dim(` ${fixableCount} item(s) can be auto-removed with: pnpm doctor:fix\n`),
|
|
403
|
+
);
|
|
404
|
+
} else {
|
|
405
|
+
console.log();
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (errorCount > 0) process.exit(1);
|