Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 94 additions & 1 deletion tools/generate-app-templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ import { spawnSync } from "node:child_process";
import {
existsSync,
mkdirSync,
readdirSync,
readFileSync,
rmSync,
writeFileSync,
} from "node:fs";
import { join, resolve } from "node:path";
import { basename, join, resolve } from "node:path";

const ROOT = resolve(import.meta.dirname, "..");

Expand All @@ -39,6 +40,17 @@ const DATABRICKS_CLI = process.env.DATABRICKS_CLI ?? "databricks";
const APPKIT_SECTION_START = "<!-- appkit-start -->";
const APPKIT_SECTION_END = "<!-- appkit-end -->";

/**
* Known query result types for template SQL files.
* The type generator cannot infer these without a warehouse connection,
* so we hardcode them here to fix up `result: unknown` in appKitTypes.d.ts.
*/
const QUERY_RESULT_TYPES: Record<string, string> = {
hello_world: "Array<{ value: string }>",
mocked_sales:
"Array<{ month: string; month_num: number; revenue: number; expenses: number; customers: number }>",
};

interface AppTemplate {
/** Output directory name and --name passed to databricks apps init */
name: string;
Expand Down Expand Up @@ -191,6 +203,87 @@ function postProcess(appDir: string, app: AppTemplate): void {
"host: https://your-workspace.cloud.databricks.com",
);
writeFileSync(databricksYmlPath, fixedYml);

// 4. Fix `result: unknown` in appKitTypes.d.ts — the type generator falls back
// to `unknown` when no warehouse is available during template generation.
fixAppKitTypes(appDir);
}

/**
* Ensures appKitTypes.d.ts has correct result types. When no warehouse is
* available, the type generator either exits early (file missing) or falls
* back to `result: unknown`. This function handles both cases.
*/
function fixAppKitTypes(appDir: string): void {
const typesPath = join(appDir, "client/src/appKitTypes.d.ts");
const queriesDir = join(appDir, "config/queries");

if (!existsSync(queriesDir)) return;

if (existsSync(typesPath)) {
// Patch existing file: replace `result: unknown` with known types
let content = readFileSync(typesPath, "utf-8");
let replaced = false;

for (const [queryName, resultType] of Object.entries(QUERY_RESULT_TYPES)) {
const pattern = new RegExp(
`(${queryName}:\\s*\\{[\\s\\S]*?)result:\\s*unknown;`,
);
if (pattern.test(content)) {
content = content.replace(pattern, `$1result: ${resultType};`);
replaced = true;
}
}

if (replaced) {
writeFileSync(typesPath, content);
console.log(` Fixed appKitTypes.d.ts result types`);
}
} else {
// File missing (no warehouse ID → type generator exited early).
// Generate it from scratch by scanning SQL files.
const sqlFiles = readdirSync(queriesDir).filter((f) => f.endsWith(".sql"));
if (sqlFiles.length === 0) return;

const queryEntries = sqlFiles.map((file) => {
const queryName = basename(file, ".sql");
const sql = readFileSync(join(queriesDir, file), "utf-8");

// Extract :paramName placeholders (same regex as the real type generator)
const params = [
...new Set([...sql.matchAll(/:([a-zA-Z_]\w*)/g)].map((m) => m[1])),
];

const paramLines = params
.map(
(p) =>
` /** any - use sql.*() */\n ${p}: SQLTypeMarker;`,
)
.join("\n");
const paramSection = params.length
? `{\n${paramLines}\n }`
: "Record<string, never>";

const resultType = QUERY_RESULT_TYPES[queryName] ?? "unknown";

return ` ${queryName}: {\n name: "${queryName}";\n parameters: ${paramSection};\n result: ${resultType};\n }`;
});

const content = `// Auto-generated by AppKit - DO NOT EDIT
// Generated by 'npx @databricks/appkit generate-types' or Vite plugin during build
import "@databricks/appkit-ui/react";
import type { SQLTypeMarker, SQLStringMarker, SQLNumberMarker, SQLBooleanMarker, SQLBinaryMarker, SQLDateMarker, SQLTimestampMarker } from "@databricks/appkit-ui/js";

declare module "@databricks/appkit-ui/react" {
interface QueryRegistry {
${queryEntries.join(";\n")};\n }
}
`;

mkdirSync(join(appDir, "client/src"), { recursive: true });
writeFileSync(typesPath, content);
console.log(` Generated appKitTypes.d.ts with ${sqlFiles.length} queries`);
}
}

/**
Expand Down
Loading