Flatten the repo: move everything out of app/ to the root

Now that the CLI and the Next.js app are one package, nesting it inside
app/ served no purpose - the repo root itself becomes the published
npm package. Merges app/.gitignore and app/README.md into the root
versions, drops the now-duplicate app/LICENSE, and updates path
references (README, docs/ARCHITECTURE.md, docs/CONFIG_REFERENCE.md,
package.json's repository.directory) that assumed the app/ nesting.

Also fixes a real bug this surfaced: the in-app docs viewer resolved
docs/ relative to process.cwd(), which only worked by accident when the
CLI happened to be invoked from app/'s parent directory. A first attempt
at fixing it with import.meta.dirname broke instead, for the same
cross-module-graph reason config-path resolution already documented -
Next compiles Route Handlers through a separate module graph that
doesn't preserve source-relative import.meta paths. Fixed by exposing
the app root via TRIGGERSHELL_APP_ROOT (set once in server.ts, where
import.meta *does* resolve correctly), the same pattern already used
for TRIGGERSHELL_CONFIG_PATH.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-16 11:14:01 +02:00
co-authored by Claude Sonnet 5
parent 30350d80f4
commit 3f379ca2ac
123 changed files with 65 additions and 104 deletions
+65
View File
@@ -0,0 +1,65 @@
import { z } from "zod";
import type { ScriptConfig, VariableConfig } from "../config/schema";
function fieldSchema(variable: VariableConfig): z.ZodTypeAny {
let field: z.ZodTypeAny;
switch (variable.type) {
case "string": {
let s = z.string();
// `required` alone has to reject "" even when the author didn't set an explicit
// minLength - otherwise an empty string satisfies a bare z.string() and the run starts
// with a "required" field effectively unset.
const minLength =
variable.minLength ?? (variable.required ? 1 : undefined);
if (minLength !== undefined) s = s.min(minLength);
if (variable.maxLength !== undefined) s = s.max(variable.maxLength);
if (variable.pattern) s = s.regex(new RegExp(variable.pattern));
field = s;
break;
}
case "number": {
let n = z.number();
if (variable.min !== undefined) n = n.min(variable.min);
if (variable.max !== undefined) n = n.max(variable.max);
field = n;
break;
}
case "boolean":
field = z.boolean();
break;
case "enum":
field = z.enum(variable.choices as [string, ...string[]]);
break;
case "multiselect": {
let arr = z.array(z.enum(variable.choices as [string, ...string[]]));
if (variable.required) arr = arr.min(1);
field = arr;
break;
}
}
if (!variable.required) {
field = field.optional().or(z.literal(""));
}
return field;
}
/** Builds one Zod object schema from a list of variable definitions - the single source of truth
* imported by both the client form resolver and the server-side run-creation handler. */
export function buildVariableSchemaFromList(
variables: readonly VariableConfig[],
) {
const shape: Record<string, z.ZodTypeAny> = {};
for (const variable of variables) {
shape[variable.name] = fieldSchema(variable);
}
return z.object(shape);
}
export function buildVariableSchema(script: ScriptConfig) {
return buildVariableSchemaFromList(script.variables);
}
export type VariableValues = Record<string, unknown>;