feat: centralize application framework primitives
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { runBuild } from "./build.ts";
|
||||
import { runTypecheck } from "./types.ts";
|
||||
|
||||
async function runScript(root: string, name: string): Promise<void> {
|
||||
const child = Bun.spawn(["bun", "run", name], {
|
||||
cwd: root,
|
||||
stdin: "inherit",
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
});
|
||||
const code = await child.exited;
|
||||
if (code !== 0) throw new Error(`WRN-CHECK: '${name}' failed with exit code ${code}`);
|
||||
}
|
||||
|
||||
/** Canonical generated-artifact-aware application verification pipeline. */
|
||||
export async function runCheck(appRoot: string): Promise<void> {
|
||||
const root = resolve(appRoot);
|
||||
const manifest = JSON.parse(readFileSync(resolve(root, "package.json"), "utf8")) as {
|
||||
scripts?: Record<string, string>;
|
||||
};
|
||||
await runBuild(root);
|
||||
if (!(await runTypecheck(root))) throw new Error("WRN-CHECK: application typecheck failed");
|
||||
for (const name of ["lint", "test", "format:check"] as const) {
|
||||
if (manifest.scripts?.[name]) await runScript(root, name);
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,7 @@ Thumbs.db
|
||||
"doctor": "wrnexus doctor .",
|
||||
"analyze": "wrnexus analyze .",
|
||||
"inspect": "wrnexus inspect packages .",
|
||||
"check": "bun run typecheck && bun run lint && bun run test && bun run format:check"
|
||||
"check": "wrnexus check ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/ai": "${scaffoldFrameworkRange}",
|
||||
@@ -227,8 +227,13 @@ export default tseslint.config(
|
||||
dist/
|
||||
.wrnexus/
|
||||
**/.wrnexus/
|
||||
.superpowers/
|
||||
.claude/
|
||||
*.log
|
||||
CLAUDE.md
|
||||
app/routes.gen.ts
|
||||
app/types/*.generated.*
|
||||
app/db/queries.gen.ts
|
||||
`,
|
||||
".editorconfig": `root = true
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* (`databases.<name>`), with files under `app/db/<name>/`.
|
||||
*/
|
||||
|
||||
import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
||||
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
||||
import { basename, join, resolve } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import {
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
type PackageMigrationDefinition,
|
||||
} from "@wrnexus/plugin";
|
||||
import { loadAppConfig, type AppConfig } from "@wrnexus/styles";
|
||||
import { writeGeneratedFile } from "./write-generated.ts";
|
||||
import {
|
||||
generateQueriesFile,
|
||||
analyzeMigrations,
|
||||
@@ -156,7 +157,7 @@ export async function regenerateQueries(
|
||||
.flatMap((f) => parseQueries(readFileSync(join(queriesDir, f), "utf8")));
|
||||
const refs = await loadModelRefs(dbBase);
|
||||
const code = generateQueriesFile(queries, refs, dialectOf(driver));
|
||||
writeFileSync(join(dbBase, "queries.gen.ts"), code, "utf8");
|
||||
writeGeneratedFile(join(dbBase, "queries.gen.ts"), code);
|
||||
return queries.length;
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ Usage:
|
||||
wrnexus generate types [app-dir] Generate application-wide route/component/key types
|
||||
wrnexus routes [app-dir] Generate typed named routes
|
||||
wrnexus typecheck [app-dir] Generate types and check TypeScript plus every .wrn file
|
||||
wrnexus check [app-dir] Build/generate, typecheck, lint, test, and format-check
|
||||
wrnexus mobile add <package...> Install Capacitor or Expo native packages
|
||||
wrnexus mobile compile Compile .wrn pages into native Expo routes
|
||||
wrnexus native list List cross-platform native capabilities
|
||||
@@ -239,6 +240,14 @@ async function main(): Promise<void> {
|
||||
else console.log("✓ Application types are valid");
|
||||
break;
|
||||
}
|
||||
case "check": {
|
||||
const appRoot = rest.find((arg) => !arg.startsWith("--")) ?? ".";
|
||||
bootstrapProfile(appRoot, "production", rest);
|
||||
const { runCheck } = await import("./check.ts");
|
||||
await runCheck(appRoot);
|
||||
console.log("✓ Application check passed");
|
||||
break;
|
||||
}
|
||||
case "eject": {
|
||||
const { runEject } = await import("./eject.ts");
|
||||
const args = rest.filter((a) => !a.startsWith("--"));
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
* startup; also exposed via `wrnexus generate routes`.
|
||||
*/
|
||||
|
||||
import { writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { buildRouter, generateRoutesFile } from "@wrnexus/router";
|
||||
import { writeGeneratedFile } from "./write-generated.ts";
|
||||
|
||||
/** Regenerate `app/routes.gen.ts`. Returns the number of page routes. */
|
||||
export function regenerateRoutes(appDir: string): number {
|
||||
const router = buildRouter(appDir);
|
||||
const code = generateRoutesFile(router.pages);
|
||||
writeFileSync(join(appDir, "routes.gen.ts"), code, "utf8");
|
||||
writeGeneratedFile(join(appDir, "routes.gen.ts"), code);
|
||||
return router.pages.length;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { generate, generateTargets } from "@wrnexus/compiler";
|
||||
import { regenerateRoutes } from "./routes.ts";
|
||||
import { loadAppConfig } from "@wrnexus/styles";
|
||||
import { createPluginRunner, discoverPlugins, type PluginContributions } from "@wrnexus/plugin";
|
||||
import { writeGeneratedFile } from "./write-generated.ts";
|
||||
|
||||
function files(root: string, predicate: (path: string) => boolean): string[] {
|
||||
if (!existsSync(root)) return [];
|
||||
@@ -309,7 +310,7 @@ declare namespace WRNexusGenerated {
|
||||
`;
|
||||
mkdirSync(typeDir, { recursive: true });
|
||||
const output = join(typeDir, "wrnexus.generated.d.ts");
|
||||
writeFileSync(output, code, "utf8");
|
||||
writeGeneratedFile(output, code);
|
||||
// `.d.ts` contents are exempt from checking under `skipLibCheck: true` (set in the
|
||||
// repo/app tsconfig), so the per-block assertions are written into a real `.ts` file
|
||||
// instead — only genuine `.ts`/`.tsx` sources are compiled and checked.
|
||||
@@ -321,7 +322,7 @@ declare namespace WRNexusGenerated {
|
||||
${apiBlockAssertions(pageAsts, apiContracts, app)}
|
||||
export {};
|
||||
`;
|
||||
writeFileSync(join(typeDir, "wrnexus.generated.api-checks.ts"), apiChecksCode, "utf8");
|
||||
writeGeneratedFile(join(typeDir, "wrnexus.generated.api-checks.ts"), apiChecksCode);
|
||||
writePluginArtifacts(root, pluginContributions);
|
||||
return {
|
||||
file: relative(root, output).replace(/\\/g, "/"),
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
||||
|
||||
/** Write generated UTF-8 output only when bytes changed, preserving mtimes on no-op builds. */
|
||||
export function writeGeneratedFile(path: string, content: string): boolean {
|
||||
const normalized = content.replace(/\r\n/g, "\n");
|
||||
if (existsSync(path) && readFileSync(path, "utf8").replace(/\r\n/g, "\n") === normalized) {
|
||||
return false;
|
||||
}
|
||||
writeFileSync(path, normalized, "utf8");
|
||||
return true;
|
||||
}
|
||||
Reference in New Issue
Block a user