Skip to content

Commit 5f1ddb8

Browse files
authored
fix: pass connection string w/ studio-mode flush operation (#852)
- **pass express url to studio-ui** - **auth bypass config for express backend** - **make plugin-configured pouchdb available** - **add caution against 'converstaion-local' comments** - **null check** - **add optional connection string for pack request...**
2 parents a1ad0bc + bad3a3c commit 5f1ddb8

File tree

11 files changed

+104
-43
lines changed

11 files changed

+104
-43
lines changed

agent/u.working-agreement.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,23 @@ eg: `a.aside.perf.many-round-trips-in-reviews-lookup.md`, `a.aside.security.addC
8080

8181
The agent should expect the user to review these asides async to the current main workflow.
8282

83+
## Durable vs Ephemeral Content
84+
85+
In conversational flow and working documents, it can be useful to 'refer back' to the context of discussion.
86+
87+
But when writing source code, documentation, or other 'durable' content, please refrain from leaving conversational notes that are particular to the current moment. The below diff illustrates an example of poor behaviour in this respect. The inserted comment informs the current reader of the diff - ok - but it is just noise for future readers of the file.
88+
89+
```diff - poor example
90+
85 - // Create course database connection
91+
86 - const courseDbUrl = `${dbUrl}/${dbName}`;
92+
85 + // courseDbUrl is already defined above
93+
```
94+
95+
```diff - preferred
96+
85 - // Create course database connection
97+
86 - const courseDbUrl = `${dbUrl}/${dbName}`;
98+
```
99+
83100
# Coda
84101

85102
This document and flow are experimental. If, experientially, it feels like it is not working, we can change it. Open to suggestions!

packages/cli/src/commands/studio.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,8 @@ async function launchStudio(coursePath: string, options: StudioOptions) {
200200
const studioUIPort = await startStudioUIServer(
201201
couchDBManager.getConnectionDetails(),
202202
unpackResult,
203-
studioUIPath
203+
studioUIPath,
204+
expressResult.url
204205
);
205206

206207
console.log(chalk.green(`✅ Studio session ready!`));
@@ -406,7 +407,8 @@ interface UnpackResult {
406407
async function startStudioUIServer(
407408
connectionDetails: ConnectionDetails,
408409
unpackResult: UnpackResult,
409-
studioPath: string
410+
studioPath: string,
411+
expressApiUrl?: string
410412
): Promise<number> {
411413
// Serve from built dist directory if it exists, otherwise fallback to source
412414
const distPath = path.join(studioPath, 'dist');
@@ -457,6 +459,9 @@ async function startStudioUIServer(
457459
name: '${unpackResult.databaseName}',
458460
courseId: '${unpackResult.courseId}',
459461
originalCourseId: '${unpackResult.courseId}'
462+
},
463+
express: {
464+
url: '${expressApiUrl || 'http://localhost:3000'}'
460465
}
461466
};
462467
</script>
@@ -485,6 +490,9 @@ async function startStudioUIServer(
485490
name: '${unpackResult.databaseName}',
486491
courseId: '${unpackResult.courseId}',
487492
originalCourseId: '${unpackResult.courseId}'
493+
},
494+
express: {
495+
url: '${expressApiUrl || 'http://localhost:3000'}'
488496
}
489497
};
490498
</script>
@@ -593,6 +601,9 @@ async function startExpressBackend(
593601
};
594602

595603
try {
604+
// Set NODE_ENV for studio mode authentication bypass
605+
process.env.NODE_ENV = 'studio';
606+
596607
// Create Express app using factory
597608
const app = createExpressApp(config);
598609

packages/common/src/wire-format.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ export interface PackCourse extends IServerRequest {
143143
type: ServerRequestType.PACK_COURSE;
144144
courseId: string;
145145
outputPath?: string;
146+
couchdbUrl?: string; // Optional full CouchDB connection URL for studio mode
146147
response: {
147148
status: Status;
148149
ok: boolean;

packages/db/src/impl/couch/pouchdb-setup.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ PouchDB.plugin(PouchDBAuth);
88

99
// Configure PouchDB globally
1010
PouchDB.defaults({
11-
ajax: {
12-
timeout: 60000,
13-
},
11+
// ajax: {
12+
// timeout: 60000,
13+
// },
1414
});
1515

1616
export default PouchDB;

packages/db/src/pouch/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Export configured PouchDB instance
2+
export { default } from '../impl/couch/pouchdb-setup.js';

packages/db/tsup.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export default defineConfig({
44
entry: [
55
'src/index.ts',
66
'src/core/index.ts',
7+
'src/pouch/index.ts',
78
'src/impl/couch/index.ts',
89
'src/impl/static/index.ts',
910
'src/util/packer/index.ts',

packages/express/src/app-factory.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,8 @@ export function createExpressApp(config: AppConfig): express.Application {
214214

215215
body.response = await packCourse({
216216
courseId: body.courseId,
217-
outputPath: body.outputPath
217+
outputPath: body.outputPath,
218+
couchdbUrl: body.couchdbUrl
218219
});
219220
res.json(body.response);
220221
}

packages/express/src/client-requests/pack-requests.ts

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { Status } from '@vue-skuilder/common';
22
import logger from '../logger.js';
33
import ENV from '../utils/env.js';
4-
import PouchDb from 'pouchdb';
4+
import PouchDB from 'pouchdb';
55

66
interface PackCourseData {
77
courseId: string;
88
outputPath?: string;
9+
couchdbUrl?: string;
910
}
1011

1112
interface PackCourseResponse {
@@ -29,9 +30,20 @@ export async function packCourse(data: PackCourseData): Promise<PackCourseRespon
2930
// Use CouchDBToStaticPacker directly from db package
3031
const { CouchDBToStaticPacker } = await import('@vue-skuilder/db');
3132

32-
// Create database connection URL
33-
const dbUrl = `${ENV.COUCHDB_PROTOCOL}://${ENV.COUCHDB_ADMIN}:${ENV.COUCHDB_PASSWORD}@${ENV.COUCHDB_SERVER}`;
34-
const dbName = `coursedb-${data.courseId}`;
33+
// Create database connection URL - use provided couchdbUrl if available (studio mode)
34+
logger.info(`Pack request data: ${JSON.stringify(data, null, 2)}`);
35+
let courseDbUrl: string;
36+
if (data.couchdbUrl) {
37+
courseDbUrl = data.couchdbUrl;
38+
logger.info(`Using provided CouchDB URL: "${courseDbUrl}"`);
39+
} else {
40+
// Fallback to ENV configuration for production mode
41+
logger.info(`ENV values - Protocol: "${ENV.COUCHDB_PROTOCOL}", Admin: "${ENV.COUCHDB_ADMIN}", Password: "${ENV.COUCHDB_PASSWORD}", Server: "${ENV.COUCHDB_SERVER}"`);
42+
const dbUrl = `${ENV.COUCHDB_PROTOCOL}://${ENV.COUCHDB_ADMIN}:${ENV.COUCHDB_PASSWORD}@${ENV.COUCHDB_SERVER}`;
43+
const dbName = `coursedb-${data.courseId}`;
44+
courseDbUrl = `${dbUrl}/${dbName}`;
45+
logger.info(`Constructed dbUrl from ENV: "${courseDbUrl}"`);
46+
}
3547

3648
// Determine output path based on environment and provided path
3749
let outputPath: string;
@@ -56,7 +68,7 @@ export async function packCourse(data: PackCourseData): Promise<PackCourseRespon
5668
process.cwd();
5769
}
5870

59-
logger.info(`Packing course ${data.courseId} from ${dbName} to ${outputPath}`);
71+
logger.info(`Packing course ${data.courseId} from ${courseDbUrl} to ${outputPath}`);
6072

6173
// Clean up existing output directory for replace-in-place functionality
6274
const fsExtra = await import('fs-extra');
@@ -72,9 +84,6 @@ export async function packCourse(data: PackCourseData): Promise<PackCourseRespon
7284
// Continue anyway - the write operation might still succeed
7385
}
7486

75-
// Create course database connection
76-
const courseDbUrl = `${dbUrl}/${dbName}`;
77-
7887
// Initialize packer and perform pack operation with file writing
7988
const packer = new CouchDBToStaticPacker();
8089

@@ -132,7 +141,16 @@ export async function packCourse(data: PackCourseData): Promise<PackCourseRespon
132141
};
133142

134143
const fsAdapter = await createFsAdapter();
135-
const packResult = await packer.packCourseToFiles(new PouchDb(courseDbUrl), data.courseId, outputPath, fsAdapter);
144+
145+
// Use regular PouchDB for simple data reading
146+
logger.info(`Creating PouchDB instance with URL: ${courseDbUrl}`);
147+
logger.info(`PouchDB constructor available: ${typeof PouchDB}`);
148+
logger.info(`PouchDB adapters: ${JSON.stringify(Object.keys((PouchDB as any).adapters || {}))}`);
149+
150+
const courseDb = new PouchDB(courseDbUrl);
151+
logger.info(`PouchDB instance created, adapter: ${(courseDb as any).adapter}`);
152+
153+
const packResult = await packer.packCourseToFiles(courseDb, data.courseId, outputPath, fsAdapter);
136154

137155
const duration = Date.now() - startTime;
138156

packages/studio-ui/src/api/index.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import { ServerRequest, ServerRequestType, PackCourse } from '@vue-skuilder/common';
22

33
async function postWithResult<T extends ServerRequest>(request: Omit<T, 'response' | 'user'>): Promise<T['response']> {
4-
const response = await fetch('http://localhost:3000/', {
4+
// Get Express API URL from studio configuration
5+
const studioConfig = (window as any).STUDIO_CONFIG;
6+
const expressUrl = studioConfig?.express?.url || 'http://localhost:3000/';
7+
8+
console.log('🚀 Sending request to:', expressUrl);
9+
console.log('📦 Request payload:', JSON.stringify(request, null, 2));
10+
11+
const response = await fetch(expressUrl, {
512
method: 'POST',
613
headers: {
714
'Content-Type': 'application/json',
@@ -13,18 +20,33 @@ async function postWithResult<T extends ServerRequest>(request: Omit<T, 'respons
1320
throw new Error(`HTTP error! status: ${response.status}`);
1421
}
1522

16-
return await response.json();
23+
const result = await response.json();
24+
console.log('📥 Response received:', JSON.stringify(result, null, 2));
25+
return result;
1726
}
1827

1928
export async function flushCourse(courseId: string, outputPath?: string) {
2029
// Get the original course ID for the output path
2130
// courseId here is the decorated database name, we need the original ID for the path
2231
const studioConfig = (window as any).STUDIO_CONFIG;
32+
console.log('🎯 Studio config:', JSON.stringify(studioConfig, null, 2));
33+
2334
const originalCourseId = studioConfig?.database?.originalCourseId || courseId;
35+
console.log('📋 Original course ID:', originalCourseId);
36+
37+
// Build CouchDB URL from studio configuration with credentials
38+
const couchdbConfig = studioConfig?.couchdb;
39+
const couchdbUrl = couchdbConfig
40+
? `http://${couchdbConfig.username}:${couchdbConfig.password}@${couchdbConfig.url.replace(/^https?:\/\//, '').replace(/\/$/, '')}/coursedb-${courseId}`
41+
: undefined;
42+
43+
console.log('🗄️ CouchDB config:', couchdbConfig);
44+
console.log('🔗 Constructed CouchDB URL:', couchdbUrl);
2445

2546
return await postWithResult<PackCourse>({
2647
type: ServerRequestType.PACK_COURSE,
2748
courseId,
2849
outputPath: outputPath ? outputPath : `./public/static-courses/${originalCourseId}`,
50+
couchdbUrl: couchdbUrl,
2951
});
3052
}

packages/studio-ui/src/components/StudioFlush.vue

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
<template>
2-
<v-btn
3-
color="success"
4-
:loading="flushing"
5-
:disabled="flushing"
6-
@click="handleFlush"
7-
>
2+
<v-btn color="success" :loading="flushing" :disabled="flushing" @click="handleFlush">
83
<v-icon start>mdi-content-save</v-icon>
94
Flush to Static
105
</v-btn>
@@ -16,37 +11,29 @@
1611
<v-icon :color="dialogIcon.color" class="me-2">{{ dialogIcon.icon }}</v-icon>
1712
{{ dialogTitle }}
1813
</v-card-title>
19-
14+
2015
<v-card-text>
2116
<div v-if="flushing">
2217
<v-progress-linear indeterminate class="mb-4" />
2318
<p>{{ flushStatus }}</p>
2419
</div>
25-
20+
2621
<div v-else-if="flushError">
2722
<v-alert type="error" class="mb-4">
2823
{{ flushError }}
2924
</v-alert>
3025
<p>The flush operation failed. Please check the console for more details.</p>
3126
</div>
32-
27+
3328
<div v-else>
34-
<v-alert type="success" class="mb-4">
35-
Course successfully saved to static files!
36-
</v-alert>
29+
<v-alert type="success" class="mb-4"> Course successfully saved to static files! </v-alert>
3730
<p>Your changes have been packed and saved to the course directory.</p>
3831
</div>
3932
</v-card-text>
40-
33+
4134
<v-card-actions>
4235
<v-spacer />
43-
<v-btn
44-
v-if="!flushing"
45-
color="primary"
46-
@click="showDialog = false"
47-
>
48-
Close
49-
</v-btn>
36+
<v-btn v-if="!flushing" color="primary" @click="showDialog = false"> Close </v-btn>
5037
</v-card-actions>
5138
</v-card>
5239
</v-dialog>
@@ -86,23 +73,22 @@ async function handleFlush() {
8673
flushing.value = true;
8774
flushError.value = null;
8875
showDialog.value = true;
89-
76+
9077
try {
9178
flushStatus.value = 'Connecting to CLI...';
9279
9380
const result = await flushCourse(props.courseId);
9481
95-
if (!result.ok) {
82+
if (result === null) {
83+
throw new Error('null result from flushCourse');
84+
} else if (!result.ok) {
9685
throw new Error(result.errorText ?? 'Unknown error');
9786
}
98-
9987
} catch (error) {
10088
console.error('Flush failed:', error);
10189
flushError.value = error instanceof Error ? error.message : 'Unknown error occurred';
10290
} finally {
10391
flushing.value = false;
10492
}
10593
}
106-
107-
108-
</script>
94+
</script>

0 commit comments

Comments
 (0)