Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
// Build-time guard: refuse to start a build the installed tree cannot finish.
|
||||
//
|
||||
// The desktop workspace's dependencies are hoisted to the repo-root
|
||||
// `node_modules`, so a root install that only covers *part* of the workspace
|
||||
// graph leaves this app importable-looking but unbuildable. The guard exists to
|
||||
// turn that into one actionable line ("run npm ci from the repo root") instead
|
||||
// of a failure deep inside vite.
|
||||
//
|
||||
// It runs from `prebuild`, ahead of `npm run clean`, so a tree that cannot
|
||||
// build is rejected before the build starts deleting its own outputs. `build`
|
||||
// re-runs it for anyone invoking the build steps directly; the check is pure
|
||||
// filesystem lookups, so paying for it twice costs nothing.
|
||||
|
||||
import { existsSync, readFileSync } from "fs"
|
||||
import { createRequire } from "module"
|
||||
import { resolve, join, dirname } from "path"
|
||||
import { isMain } from "./utils.mjs"
|
||||
|
||||
// Packages the build *consumes*, as opposed to merely declares. Each one is
|
||||
// load-bearing for a distinct build step, and each one has been observed
|
||||
// missing from a partial root install:
|
||||
//
|
||||
// vite — bundles the renderer (`vite build`).
|
||||
// katex — `src/styles.css` imports `katex/dist/katex.min.css`, so
|
||||
// the CSS transform fails before a single chunk is emitted.
|
||||
// electron — the runtime electron-builder packages; without it `pack`
|
||||
// cannot produce an unpacked app at all.
|
||||
// electron-builder — the packager `npm run builder` shells out to.
|
||||
//
|
||||
// Checking only `vite` (the original guard) passes a tree missing any of the
|
||||
// others, which is how an incomplete install reached `vite build` and died on
|
||||
// an unresolved `katex/dist/katex.min.css` with no hint that the install — not
|
||||
// the source — was at fault (#86443).
|
||||
//
|
||||
// These four are the documented floor — always checked, even when the app's
|
||||
// package.json cannot be read. The full class is wider: EVERY non-optional
|
||||
// package the workspace manifest declares is something the build may import
|
||||
// (`vite.config.ts` pulls `@rolldown/plugin-babel`, `@vitejs/plugin-react`,
|
||||
// `@tailwindcss/vite`; `bundle-electron-main.mjs` pulls `esbuild`; the renderer
|
||||
// imports the rest). A hand-maintained list drifts the moment a new import
|
||||
// lands, so `checkRootInstall` unions the floor with the manifest's declared
|
||||
// `dependencies` + `devDependencies` — a partial install is refused whichever
|
||||
// package it happened to drop. `optionalDependencies` are excluded by design:
|
||||
// npm legitimately skips them (platform-gated natives like `get-windows`).
|
||||
const BUILD_CRITICAL_PACKAGES = ["vite", "katex", "electron", "electron-builder"]
|
||||
export { BUILD_CRITICAL_PACKAGES }
|
||||
|
||||
// Resolve the way Node's own lookup does — walk `node_modules` upward — rather
|
||||
// than through `require.resolve`. A package whose `exports` map does not expose
|
||||
// `./package.json` is not resolvable by path even when correctly installed, and
|
||||
// that must not read as "missing". Scoped names (`@scope/name`) are a nested
|
||||
// directory under `node_modules`, which `join` handles.
|
||||
function packageIsInstalled(name, fromDir) {
|
||||
let dir = fromDir
|
||||
for (;;) {
|
||||
if (existsSync(join(dir, "node_modules", name, "package.json"))) return true
|
||||
const parent = dirname(dir)
|
||||
if (parent === dir) return false
|
||||
dir = parent
|
||||
}
|
||||
}
|
||||
|
||||
// Every package the workspace manifest at `appDir` declares as required
|
||||
// (`dependencies` + `devDependencies`; never `optionalDependencies`). An
|
||||
// unreadable or malformed manifest yields [] — the floor still applies, and
|
||||
// the build's own manifest read fails loudly on its own.
|
||||
export function requiredPackages(appDir) {
|
||||
try {
|
||||
const manifest = JSON.parse(readFileSync(join(appDir, "package.json"), "utf8"))
|
||||
return [
|
||||
...Object.keys(manifest.dependencies ?? {}),
|
||||
...Object.keys(manifest.devDependencies ?? {}),
|
||||
]
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
// Pure check — returns { ok: true } or { ok: false, error: "..." }.
|
||||
// Kept side-effect-free so it can be unit tested without spawning a process.
|
||||
export function checkRootInstall(appDir, rootDir) {
|
||||
const wanted = [...new Set([...BUILD_CRITICAL_PACKAGES, ...requiredPackages(appDir)])]
|
||||
const missing = wanted.filter(pkg => !packageIsInstalled(pkg, appDir))
|
||||
if (missing.length > 0) {
|
||||
return {
|
||||
ok: false,
|
||||
error:
|
||||
`the desktop build needs ${missing.join(", ")}, which the current install ` +
|
||||
`does not provide. A partial root install leaves the workspace looking ` +
|
||||
`present while the build cannot complete. Reinstall from the repo root: ` +
|
||||
`cd ${rootDir} && npm ci`
|
||||
}
|
||||
}
|
||||
|
||||
// `vite.config.ts` aliases react/react-dom to whatever this workspace resolves,
|
||||
// and React refuses to run when the two come from different installed copies
|
||||
// ("Minified React error #527" — it throws before the first paint, so the app
|
||||
// window stays blank). npm stays silent about the split because the hoisted
|
||||
// react still satisfies react-dom's caret peer range. Fail the build loudly
|
||||
// instead of shipping a white screen.
|
||||
const requireFromApp = createRequire(join(appDir, "package.json"))
|
||||
const installedVersion = pkg =>
|
||||
JSON.parse(readFileSync(requireFromApp.resolve(`${pkg}/package.json`), "utf8")).version
|
||||
|
||||
let react
|
||||
let reactDom
|
||||
try {
|
||||
react = installedVersion("react")
|
||||
reactDom = installedVersion("react-dom")
|
||||
} catch (err) {
|
||||
// Both are in BUILD_CRITICAL_PACKAGES' spirit but not its list: they are
|
||||
// checked by version, and an unreadable package.json is a broken install
|
||||
// rather than an absent one. Report it as such instead of throwing.
|
||||
return {
|
||||
ok: false,
|
||||
error: `could not read the installed react/react-dom versions (${err.message}). Reinstall from the repo root: cd ${rootDir} && npm ci`
|
||||
}
|
||||
}
|
||||
|
||||
if (react !== reactDom) {
|
||||
return {
|
||||
ok: false,
|
||||
error:
|
||||
`react@${react} / react-dom@${reactDom} version mismatch — React would fail ` +
|
||||
`with error #527 and render a blank window. Pin both to the same version ` +
|
||||
`in ${join(appDir, "package.json")}, then reinstall: cd ${rootDir} && npm ci`
|
||||
}
|
||||
}
|
||||
|
||||
return { ok: true }
|
||||
}
|
||||
|
||||
function main() {
|
||||
const app = resolve(import.meta.dirname, "..")
|
||||
const root = resolve(app, "..", "..")
|
||||
const result = checkRootInstall(app, root)
|
||||
|
||||
if (!result.ok) {
|
||||
console.error(`✗ assert-root-install: ${result.error}`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
if (isMain(import.meta.url)) {
|
||||
main()
|
||||
}
|
||||
Reference in New Issue
Block a user