fix: type-safe nitro editor helpers and stabilize deploy build
Local Build and Deploy / deploy (push) Failing after 29s
Local Build and Deploy / deploy (push) Failing after 29s
Rewrite getNested/updateNested without fragile any/Json inference, clear stale .next types before build, and skip env refine during compile. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
1273f9aa46
commit
b22725d3a9
3 files changed
+57
-30
No files matched your search
@@ -66,6 +66,10 @@ jobs:
|
|||||||
pnpm test
|
pnpm test
|
||||||
|
|
||||||
# 7. Next.js Build
|
# 7. Next.js Build
|
||||||
|
# Skip env refine during compile/page-data; runtime still validates via env.ts.
|
||||||
|
# Drop stale generated types that can diverge from source after incremental builds.
|
||||||
|
rm -rf .next/types .next/dev
|
||||||
|
export SKIP_ENV_VALIDATION=1
|
||||||
pnpm build
|
pnpm build
|
||||||
|
|
||||||
# 8. Fix ownership: Build first, THEN set permissions for the web server
|
# 8. Fix ownership: Build first, THEN set permissions for the web server
|
||||||
|
|||||||
@@ -39,30 +39,51 @@ interface NitroEditorDialogProps {
|
|||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: explicitly chosen here, see surrounding code
|
function updateNested(
|
||||||
function updateNested(obj: any, path: string, value: unknown): any {
|
obj: Record<string, unknown>,
|
||||||
const clone = JSON.parse(JSON.stringify(obj));
|
path: string,
|
||||||
|
value: unknown,
|
||||||
|
): Record<string, unknown> {
|
||||||
|
const clone = JSON.parse(JSON.stringify(obj)) as Record<string, unknown>;
|
||||||
const keys = path.split(".");
|
const keys = path.split(".");
|
||||||
let current = clone;
|
let current: Record<string, unknown> = clone;
|
||||||
for (let i = 0; i < keys.length - 1; i++) {
|
for (let i = 0; i < keys.length - 1; i++) {
|
||||||
if (!current[keys[i]]) current[keys[i]] = {};
|
const key = keys[i];
|
||||||
current = current[keys[i]];
|
const next = current[key];
|
||||||
|
if (next == null || typeof next !== "object" || Array.isArray(next)) {
|
||||||
|
current[key] = {};
|
||||||
|
}
|
||||||
|
current = current[key] as Record<string, unknown>;
|
||||||
}
|
}
|
||||||
current[keys[keys.length - 1]] = value;
|
current[keys[keys.length - 1]] = value;
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: explicitly chosen here, see surrounding code
|
function getNested(
|
||||||
function getNested(obj: any, path: string, fallback?: unknown): any {
|
obj: unknown,
|
||||||
|
path: string,
|
||||||
|
fallback?: unknown,
|
||||||
|
): unknown {
|
||||||
const keys = path.split(".");
|
const keys = path.split(".");
|
||||||
let current = obj;
|
let current: unknown = obj;
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
if (current == null || typeof current !== "object") return fallback;
|
if (current == null || typeof current !== "object") return fallback;
|
||||||
current = current[key];
|
current = (current as Record<string, unknown>)[key];
|
||||||
}
|
}
|
||||||
return current ?? fallback;
|
return current ?? fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function patchMetadata(
|
||||||
|
prev: unknown,
|
||||||
|
path: string,
|
||||||
|
value: unknown,
|
||||||
|
): unknown {
|
||||||
|
if (prev == null || typeof prev !== "object" || Array.isArray(prev)) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
return updateNested(prev as Record<string, unknown>, path, value);
|
||||||
|
}
|
||||||
|
|
||||||
const ALL_DIRECTIONS = [0, 1, 2, 3, 4, 5, 6, 7];
|
const ALL_DIRECTIONS = [0, 1, 2, 3, 4, 5, 6, 7];
|
||||||
const DIR_LABELS = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"];
|
const DIR_LABELS = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"];
|
||||||
|
|
||||||
@@ -220,33 +241,33 @@ export function NitroEditorDialog({
|
|||||||
|
|
||||||
// ── Visual tab helpers ──
|
// ── Visual tab helpers ──
|
||||||
|
|
||||||
const dims = metadata
|
const dims = (
|
||||||
? getNested(metadata, "logic.model.dimensions", { x: 1, y: 1, z: 0 })
|
metadata
|
||||||
: { x: 1, y: 1, z: 0 };
|
? getNested(metadata, "logic.model.dimensions", { x: 1, y: 1, z: 0 })
|
||||||
const directions: number[] = metadata
|
: { x: 1, y: 1, z: 0 }
|
||||||
? getNested(metadata, "logic.model.directions", [0])
|
) as { x: number; y: number; z: number };
|
||||||
: [0];
|
const directions = (
|
||||||
|
metadata ? getNested(metadata, "logic.model.directions", [0]) : [0]
|
||||||
|
) as number[];
|
||||||
|
|
||||||
const setDim = (axis: "x" | "y" | "z", value: string) => {
|
const setDim = (axis: "x" | "y" | "z", value: string) => {
|
||||||
const num =
|
const num =
|
||||||
axis === "z" ? parseFloat(value) || 0 : parseInt(value, 10) || 0;
|
axis === "z" ? parseFloat(value) || 0 : parseInt(value, 10) || 0;
|
||||||
setMetadata((prev: unknown) =>
|
setMetadata((prev: unknown) =>
|
||||||
updateNested(prev, `logic.model.dimensions.${axis}`, num),
|
patchMetadata(prev, `logic.model.dimensions.${axis}`, num),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleDirection = (dir: number) => {
|
const toggleDirection = (dir: number) => {
|
||||||
const current: number[] = getNested(
|
const current = getNested(metadata, "logic.model.directions", [
|
||||||
metadata,
|
0,
|
||||||
"logic.model.directions",
|
]) as number[];
|
||||||
[0],
|
|
||||||
);
|
|
||||||
const next = current.includes(dir)
|
const next = current.includes(dir)
|
||||||
? current.filter((d: number) => d !== dir)
|
? current.filter((d: number) => d !== dir)
|
||||||
: [...current, dir].sort((a: number, b: number) => a - b);
|
: [...current, dir].sort((a: number, b: number) => a - b);
|
||||||
if (next.length === 0) return;
|
if (next.length === 0) return;
|
||||||
setMetadata((prev: unknown) =>
|
setMetadata((prev: unknown) =>
|
||||||
updateNested(prev, "logic.model.directions", next),
|
patchMetadata(prev, "logic.model.directions", next),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -549,7 +570,7 @@ export function NitroEditorDialog({
|
|||||||
value={metadata.visualizationType || ""}
|
value={metadata.visualizationType || ""}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setMetadata((prev: unknown) =>
|
setMetadata((prev: unknown) =>
|
||||||
updateNested(
|
patchMetadata(
|
||||||
prev,
|
prev,
|
||||||
"visualizationType",
|
"visualizationType",
|
||||||
e.target.value,
|
e.target.value,
|
||||||
@@ -571,7 +592,7 @@ export function NitroEditorDialog({
|
|||||||
value={metadata.logicType || ""}
|
value={metadata.logicType || ""}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setMetadata((prev: unknown) =>
|
setMetadata((prev: unknown) =>
|
||||||
updateNested(prev, "logicType", e.target.value),
|
patchMetadata(prev, "logicType", e.target.value),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
className="mt-1 font-mono text-sm"
|
className="mt-1 font-mono text-sm"
|
||||||
@@ -622,7 +643,7 @@ export function NitroEditorDialog({
|
|||||||
value={(layer.z as number) ?? 0}
|
value={(layer.z as number) ?? 0}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setMetadata((prev: unknown) =>
|
setMetadata((prev: unknown) =>
|
||||||
updateNested(
|
patchMetadata(
|
||||||
prev,
|
prev,
|
||||||
`visualizations.${viz64Index}.layers.${layerId}.z`,
|
`visualizations.${viz64Index}.layers.${layerId}.z`,
|
||||||
parseInt(e.target.value, 10) || 0,
|
parseInt(e.target.value, 10) || 0,
|
||||||
@@ -638,7 +659,7 @@ export function NitroEditorDialog({
|
|||||||
value={(layer.alpha as number) ?? 255}
|
value={(layer.alpha as number) ?? 255}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setMetadata((prev: unknown) =>
|
setMetadata((prev: unknown) =>
|
||||||
updateNested(
|
patchMetadata(
|
||||||
prev,
|
prev,
|
||||||
`visualizations.${viz64Index}.layers.${layerId}.alpha`,
|
`visualizations.${viz64Index}.layers.${layerId}.alpha`,
|
||||||
parseInt(e.target.value, 10) || 0,
|
parseInt(e.target.value, 10) || 0,
|
||||||
@@ -652,7 +673,7 @@ export function NitroEditorDialog({
|
|||||||
placeholder="—"
|
placeholder="—"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setMetadata((prev: unknown) =>
|
setMetadata((prev: unknown) =>
|
||||||
updateNested(
|
patchMetadata(
|
||||||
prev,
|
prev,
|
||||||
`visualizations.${viz64Index}.layers.${layerId}.ink`,
|
`visualizations.${viz64Index}.layers.${layerId}.ink`,
|
||||||
e.target.value || undefined,
|
e.target.value || undefined,
|
||||||
@@ -737,7 +758,7 @@ export function NitroEditorDialog({
|
|||||||
.slice(0, 6);
|
.slice(0, 6);
|
||||||
const num = parseInt(hex, 16) || 0;
|
const num = parseInt(hex, 16) || 0;
|
||||||
setMetadata((prev: unknown) =>
|
setMetadata((prev: unknown) =>
|
||||||
updateNested(
|
patchMetadata(
|
||||||
prev,
|
prev,
|
||||||
`visualizations.${viz64Index}.colors.${colorId}.${layerId}.color`,
|
`visualizations.${viz64Index}.colors.${colorId}.${layerId}.color`,
|
||||||
num,
|
num,
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ describe("production deploy workflow", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
it("preserves the Next.js incremental build cache", () => {
|
it("preserves the Next.js incremental build cache", () => {
|
||||||
expect(workflow).not.toMatch(/rm\s+-rf[^\n]*\.next/);
|
// May clear .next/types or .next/dev, but must not wipe the whole .next tree.
|
||||||
|
expect(workflow).not.toMatch(/rm\s+-rf\s+\.next(?:\s|$)/);
|
||||||
expect(workflow).toContain("pnpm install --frozen-lockfile");
|
expect(workflow).toContain("pnpm install --frozen-lockfile");
|
||||||
|
expect(workflow).toContain("SKIP_ENV_VALIDATION=1");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not override onlyBuiltDependencies (uses pnpm-workspace.yaml)", () => {
|
it("does not override onlyBuiltDependencies (uses pnpm-workspace.yaml)", () => {
|
||||||
|
|||||||
Reference in new issue
Block a user