From 7224f55bfaa97769f62eda969e94ed2093c427e3 Mon Sep 17 00:00:00 2001 From: NiloCK Date: Sat, 22 Nov 2025 09:13:59 -0400 Subject: [PATCH 1/5] include cli version as input to cached build hash --- packages/cli/src/commands/studio.ts | 8 +-- packages/cli/src/utils/questions-hash.ts | 71 +++++++++++++++++------- 2 files changed, 56 insertions(+), 23 deletions(-) diff --git a/packages/cli/src/commands/studio.ts b/packages/cli/src/commands/studio.ts index 8d2f3e17c..2671647db 100644 --- a/packages/cli/src/commands/studio.ts +++ b/packages/cli/src/commands/studio.ts @@ -812,18 +812,18 @@ async function buildStudioUIWithQuestions( const buildPath = await ensureBuildDirectory(coursePath, questionsHash); try { - // Handle special cases - if (questionsHash === 'no-questions') { + // Handle special cases (now version-aware) + if (questionsHash.startsWith('no-questions-')) { console.log(chalk.gray(` No local questions detected, using default studio-ui`)); return await buildDefaultStudioUI(buildPath); } - if (questionsHash === 'empty-questions') { + if (questionsHash.startsWith('empty-questions-')) { console.log(chalk.gray(` Empty questions directory, using default studio-ui`)); return await buildDefaultStudioUI(buildPath); } - if (questionsHash === 'hash-error') { + if (questionsHash.startsWith('hash-error-')) { const hashError = createStudioBuildError( StudioBuildErrorType.QUESTIONS_HASH_ERROR, 'Questions directory could not be processed', diff --git a/packages/cli/src/utils/questions-hash.ts b/packages/cli/src/utils/questions-hash.ts index 0e5e367b1..b4208b723 100644 --- a/packages/cli/src/utils/questions-hash.ts +++ b/packages/cli/src/utils/questions-hash.ts @@ -1,47 +1,71 @@ import { createHash } from 'crypto'; import { promises as fs, existsSync } from 'fs'; import path from 'path'; +import { VERSION } from '../cli.js'; /** - * Calculate hash of src/questions/ directory contents - * Returns consistent hash based on file names, content, and modification times + * Calculate composite hash of src/questions/ directory contents + CLI version + * + * Returns consistent hash based on: + * - File names, content, and modification times in src/questions/ + * - CLI version (ensures cache invalidation when CLI updates with new studio-ui code) + * + * Cache Invalidation Strategy: + * - Questions change: Different file content/mtime → new hash → cache miss → rebuild + * - CLI updates: Different VERSION → new hash → cache miss → rebuild with new studio-ui + * - This solves the problem where CLI updates with new studio-ui features weren't + * being picked up by existing cached builds + * + * Special Cases: + * - No questions directory: Returns "no-questions-{version-hash}" + * - Empty questions directory: Returns "empty-questions-{version-hash}" + * - Hash error: Returns "hash-error-{version-hash}" */ export async function hashQuestionsDirectory(coursePath: string): Promise { const questionsPath = path.join(coursePath, 'src', 'questions'); - - // If questions directory doesn't exist, return special "no-questions" hash + + // If questions directory doesn't exist, return special "no-questions" hash with CLI version if (!existsSync(questionsPath)) { - return 'no-questions'; + const hash = createHash('sha256'); + hash.update(`no-questions:${VERSION}`); + return `no-questions-${hash.digest('hex').substring(0, 8)}`; } const hash = createHash('sha256'); - + try { // Get all files recursively, sort for consistent ordering const files = await getAllFiles(questionsPath); files.sort(); - - // If no files, return "empty-questions" hash + + // If no files, return "empty-questions" hash with CLI version if (files.length === 0) { - return 'empty-questions'; + const emptyHash = createHash('sha256'); + emptyHash.update(`empty-questions:${VERSION}`); + return `empty-questions-${emptyHash.digest('hex').substring(0, 8)}`; } - + // Hash each file's relative path, content, and mtime for (const file of files) { const relativePath = path.relative(questionsPath, file); const stat = await fs.stat(file); const content = await fs.readFile(file); - + // Include relative path, modification time, and content in hash hash.update(relativePath); hash.update(stat.mtime.toISOString()); hash.update(content); } - + + // Include CLI version in hash for cache invalidation on CLI updates + hash.update(`cli-version:${VERSION}`); + return hash.digest('hex').substring(0, 12); // First 12 chars for readability } catch (error) { console.warn(`Warning: Failed to hash questions directory: ${error}`); - return 'hash-error'; + const errorHash = createHash('sha256'); + errorHash.update(`hash-error:${VERSION}`); + return `hash-error-${errorHash.digest('hex').substring(0, 8)}`; } } @@ -50,13 +74,13 @@ export async function hashQuestionsDirectory(coursePath: string): Promise { const files: string[] = []; - + try { const entries = await fs.readdir(dirPath, { withFileTypes: true }); - + for (const entry of entries) { const fullPath = path.join(dirPath, entry.name); - + if (entry.isDirectory()) { // Recursively get files from subdirectories const subFiles = await getAllFiles(fullPath); @@ -72,12 +96,18 @@ async function getAllFiles(dirPath: string): Promise { // If directory can't be read, return empty array console.warn(`Warning: Could not read directory ${dirPath}: ${error}`); } - + return files; } /** * Get the studio build directory path for a given questions hash + * + * The questionsHash parameter already includes CLI version, creating version-aware cache directories: + * - .skuilder/studio-builds/a1b2c3d4e5f6/ (normal questions + CLI v0.1.15) + * - .skuilder/studio-builds/f6e5d4c3b2a1/ (same questions + CLI v0.1.16) + * + * This ensures that CLI updates automatically get fresh cache entries with updated studio-ui code. */ export function getStudioBuildPath(coursePath: string, questionsHash: string): string { return path.join(coursePath, '.skuilder', 'studio-builds', questionsHash); @@ -102,8 +132,11 @@ export async function ensureCacheDirectory(coursePath: string): Promise { /** * Ensure a specific build directory exists */ -export async function ensureBuildDirectory(coursePath: string, questionsHash: string): Promise { +export async function ensureBuildDirectory( + coursePath: string, + questionsHash: string +): Promise { const buildPath = getStudioBuildPath(coursePath, questionsHash); await fs.mkdir(buildPath, { recursive: true }); return buildPath; -} \ No newline at end of file +} From 4bc18ed16922acd6c620505bbe0622c2237e2825 Mon Sep 17 00:00:00 2001 From: NiloCK Date: Sat, 22 Nov 2025 09:14:19 -0400 Subject: [PATCH 2/5] ui: separtate btn group in studio-ui navbar --- packages/studio-ui/src/App.vue | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/studio-ui/src/App.vue b/packages/studio-ui/src/App.vue index cf5a4f000..4a73fd69b 100644 --- a/packages/studio-ui/src/App.vue +++ b/packages/studio-ui/src/App.vue @@ -8,14 +8,19 @@ - - +
+ mdi-magnify Browse Course mdi-card-plus Create Card @@ -27,7 +32,7 @@ mdi-file-import Bulk Import - +
From b7dafc4aedc2dee37329ae217b147f7a596903e4 Mon Sep 17 00:00:00 2001 From: NiloCK Date: Tue, 25 Nov 2025 12:15:25 -0400 Subject: [PATCH 3/5] ignore test, usage artifacts --- packages/cli/.gitignore | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/cli/.gitignore b/packages/cli/.gitignore index 7d64d151f..6fc5e4b83 100644 --- a/packages/cli/.gitignore +++ b/packages/cli/.gitignore @@ -1,3 +1,7 @@ +# test projects created by localdev test scripts testproject testproject-empty -userdb-Guest + +# artifacts from running cli +userdb-sk* +logs From 5d0bd6340185da71cd93e6b5f247e9dd874fca7c Mon Sep 17 00:00:00 2001 From: NiloCK Date: Tue, 25 Nov 2025 12:15:53 -0400 Subject: [PATCH 4/5] improve srcMap generation in edit-ui --- packages/edit-ui/vite.config.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/edit-ui/vite.config.js b/packages/edit-ui/vite.config.js index d91375886..6bd5fa3ae 100644 --- a/packages/edit-ui/vite.config.js +++ b/packages/edit-ui/vite.config.js @@ -8,6 +8,7 @@ export default defineConfig({ build: { target: 'es2020', minify: 'terser', + sourcemap: true, terserOptions: { keep_classnames: true, }, @@ -38,12 +39,12 @@ export default defineConfig({ '@vue-skuilder/db': 'VueSkuilderDb', '@vue-skuilder/common': 'VueSkuilderCommon', '@vue-skuilder/common-ui': 'VueSkuilderCommonUI', + sourcemap: true, }, // Preserve CSS in the output bundles assetFileNames: (assetInfo) => { return `assets/[name][extname]`; }, - sourcemap: true, }, }, // This is crucial for component libraries - allow CSS to be in chunks From 76ca3218b1e77a5b3e8d5274bdfd7c233bdbe0fb Mon Sep 17 00:00:00 2001 From: NiloCK Date: Tue, 25 Nov 2025 12:17:05 -0400 Subject: [PATCH 5/5] key the dataInputForm... forcing re-render and less ambiguity wrt the current DataShape. Fixes recent (unexplained) failures of cli ci e2e. --- packages/studio-ui/src/views/CreateCardView.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/studio-ui/src/views/CreateCardView.vue b/packages/studio-ui/src/views/CreateCardView.vue index 556909c1e..bcb12e45c 100644 --- a/packages/studio-ui/src/views/CreateCardView.vue +++ b/packages/studio-ui/src/views/CreateCardView.vue @@ -35,6 +35,7 @@