mock.ts was a static import and the mock switch was read from the query string at runtime, so the bundle that ships inside the daemon carried a complete fictional router and a link ending in ?dev rendered it: protected, 119 of 122 nodes alive, without a single request to the daemon. The only tell was a line in the footer. That is worse than any wrong number — there is no data at all and nothing says so. It is out of the production bundle now, which is 21 kB smaller for it. Unknown state stopped reading as good news in two more places. The kill-switch tile treated an absent plane as armed, because the check was "not none" and undefined satisfies it — the contract in the API types says the opposite. And the apply page announced "daemon auto-rolled back" from its own timer, while the daemon, seeing the state generation move, disarms and says it is NOT rolling back in the log only. Alerts moved to Settings. They are about the kill switch, apply failures, new devices and subscription expiry, and they lived at the bottom of the DNS page, while Settings mentioned them in prose with nothing to click. Findings truncation is visible now: the notice that says how many were suppressed arrives as info, and the attention list keeps only critical and warning, so past fifty findings the operator saw forty-nine and no hint of the rest. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
86 lines
3.3 KiB
TypeScript
86 lines
3.3 KiB
TypeScript
import { defineConfig } from 'vite'
|
|
import type { Plugin } from 'vite'
|
|
import react from '@vitejs/plugin-react'
|
|
|
|
// Minimal ambient for the dev-proxy target override — avoids pulling in @types/node
|
|
// just for one env read. Vite runs this file under Node where `process` exists.
|
|
declare const process: { env: Record<string, string | undefined> }
|
|
|
|
/** `src/mock.ts`, as the module graph spells it (POSIX-normalised for Windows). */
|
|
const MOCK_MODULE = 'src/mock.ts'
|
|
|
|
/**
|
|
* Refuse to emit a production bundle that contains the offline fixture backend.
|
|
*
|
|
* `src/mock.ts` describes an invented, healthy router: a full config, 122 nodes
|
|
* with 119 of them alive, "Protected". It exists so `npm run dev` renders without
|
|
* a daemon. It shipped inside the binary that goes on real hardware, switched on
|
|
* by nothing more than a `?dev` on the end of the URL — so a link someone was
|
|
* sent, or a bookmark they saved, showed an appliance in perfect health while
|
|
* making no request to the appliance at all.
|
|
*
|
|
* api.ts now loads it behind `import.meta.env.DEV`, which Vite folds to a literal
|
|
* `false` for a build, so Rollup drops the dynamic import and the module never
|
|
* enters the graph. That is a property of a build tool's optimiser, and an
|
|
* optimiser is not a promise: one refactor that makes the condition non-static
|
|
* silently puts the fixtures back. So the property is CHECKED rather than
|
|
* trusted — if `src/mock.ts` reaches any emitted chunk, the build fails here
|
|
* instead of shipping.
|
|
*/
|
|
function assertNoMockFixtures(): Plugin {
|
|
return {
|
|
name: 'shater:assert-no-mock-fixtures',
|
|
apply: 'build',
|
|
generateBundle(_options, bundle) {
|
|
const guilty: string[] = []
|
|
for (const [file, output] of Object.entries(bundle)) {
|
|
if (output.type !== 'chunk') continue
|
|
for (const id of output.moduleIds) {
|
|
if (id.replace(/\\/g, '/').endsWith(MOCK_MODULE)) guilty.push(`${file} ← ${id}`)
|
|
}
|
|
}
|
|
if (guilty.length > 0) {
|
|
this.error(
|
|
`the offline fixture backend (${MOCK_MODULE}) reached the production bundle:\n ` +
|
|
guilty.join('\n ') +
|
|
`\nFixtures describe a router that does not exist. Keep every path to them behind ` +
|
|
`\`import.meta.env.DEV\` so Rollup can drop them, and never gate them on a runtime ` +
|
|
`flag such as a query parameter.`,
|
|
)
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
// The SPA is embedded in the forked sing-box binary and served by the daemon on
|
|
// its own port. Relative base so it works under any mount path; single small
|
|
// bundle (no code-splitting) keeps the embed simple and the flash budget low.
|
|
export default defineConfig({
|
|
plugins: [react(), assertNoMockFixtures()],
|
|
base: './',
|
|
build: {
|
|
outDir: 'dist',
|
|
assetsDir: 'assets',
|
|
emptyOutDir: true,
|
|
target: 'es2020',
|
|
cssCodeSplit: false,
|
|
rollupOptions: {
|
|
output: {
|
|
manualChunks: undefined,
|
|
},
|
|
},
|
|
},
|
|
server: {
|
|
port: 5173,
|
|
// Dev without the ?mock fixture backend: proxy /api to a running shaterd so
|
|
// `npm run dev` talks to a real daemon same-origin (cookies included).
|
|
// Override the target with SHATER_API, e.g. SHATER_API=http://192.168.1.1:8088.
|
|
proxy: {
|
|
'/api': {
|
|
target: process.env.SHATER_API || 'http://127.0.0.1:8088',
|
|
changeOrigin: true,
|
|
},
|
|
},
|
|
},
|
|
})
|