@duckcodeailabs/dql-cli 1.5.1 → 1.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/apps-api.d.ts +45 -0
- package/dist/apps-api.d.ts.map +1 -1
- package/dist/apps-api.js +351 -12
- package/dist/apps-api.js.map +1 -1
- package/dist/apps-api.test.js +39 -2
- package/dist/apps-api.test.js.map +1 -1
- package/dist/assets/dql-notebook/assets/index-B5jI3I8Q.js +869 -0
- package/dist/assets/dql-notebook/assets/index-cv-O4BEj.css +1 -0
- package/dist/assets/dql-notebook/index.html +2 -2
- package/dist/block-studio-import.d.ts +51 -1
- package/dist/block-studio-import.d.ts.map +1 -1
- package/dist/block-studio-import.js +153 -18
- package/dist/block-studio-import.js.map +1 -1
- package/dist/block-studio-import.test.js +59 -1
- package/dist/block-studio-import.test.js.map +1 -1
- package/dist/local-runtime.d.ts +46 -0
- package/dist/local-runtime.d.ts.map +1 -1
- package/dist/local-runtime.js +716 -98
- package/dist/local-runtime.js.map +1 -1
- package/dist/local-runtime.test.js +61 -2
- package/dist/local-runtime.test.js.map +1 -1
- package/package.json +11 -11
- package/dist/assets/dql-notebook/assets/index-DZ2X3-OY.js +0 -862
- package/dist/assets/dql-notebook/assets/index-R3UrqjLQ.css +0 -1
package/dist/apps-api.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ interface Ctx {
|
|
|
12
12
|
path: string;
|
|
13
13
|
projectRoot: string;
|
|
14
14
|
executeSql?: (sql: string) => Promise<unknown>;
|
|
15
|
+
runNotebook?: (appId: string, notebookPath: string) => Promise<void>;
|
|
15
16
|
}
|
|
16
17
|
export declare function handleAppsApi(ctx: Ctx): Promise<boolean>;
|
|
17
18
|
type AppListEntry = {
|
|
@@ -62,6 +63,7 @@ interface AppRecommendationRequest {
|
|
|
62
63
|
interface AppCreateRequest {
|
|
63
64
|
name?: string;
|
|
64
65
|
domain?: string;
|
|
66
|
+
dashboardTitle?: string;
|
|
65
67
|
subdomain?: string;
|
|
66
68
|
groups?: string[];
|
|
67
69
|
purpose?: string;
|
|
@@ -97,5 +99,48 @@ export declare function createAppPackage(projectRoot: string, input: AppCreateRe
|
|
|
97
99
|
ok: false;
|
|
98
100
|
error: string;
|
|
99
101
|
};
|
|
102
|
+
declare function loadAppById(projectRoot: string, id: string): {
|
|
103
|
+
app: AppDocument;
|
|
104
|
+
dashboards: Array<{
|
|
105
|
+
id: string;
|
|
106
|
+
title: string;
|
|
107
|
+
description?: string;
|
|
108
|
+
itemCount: number;
|
|
109
|
+
}>;
|
|
110
|
+
notebooks: AppListEntry['notebooks'];
|
|
111
|
+
drafts: AppListEntry['drafts'];
|
|
112
|
+
aiPins: unknown[];
|
|
113
|
+
} | null;
|
|
114
|
+
export declare function listNotebookCandidates(projectRoot: string, app: AppDocument, appDir: string): Array<{
|
|
115
|
+
path: string;
|
|
116
|
+
title: string;
|
|
117
|
+
attached: boolean;
|
|
118
|
+
role?: 'source' | 'analysis' | 'supporting';
|
|
119
|
+
visibility?: NonNullable<AppDocument['visibility']>;
|
|
120
|
+
lastModified?: string;
|
|
121
|
+
}>;
|
|
122
|
+
export declare function createNotebookForApp(projectRoot: string, appId: string, input: {
|
|
123
|
+
name?: string;
|
|
124
|
+
title?: string;
|
|
125
|
+
role?: string;
|
|
126
|
+
visibility?: string;
|
|
127
|
+
template?: string;
|
|
128
|
+
}): {
|
|
129
|
+
ok: true;
|
|
130
|
+
path: string;
|
|
131
|
+
app: ReturnType<typeof loadAppById>;
|
|
132
|
+
preview: unknown;
|
|
133
|
+
} | {
|
|
134
|
+
ok: false;
|
|
135
|
+
error: string;
|
|
136
|
+
};
|
|
137
|
+
export declare function previewNotebookForApp(projectRoot: string, appId: string, notebookPath: string): {
|
|
138
|
+
ok: true;
|
|
139
|
+
preview: unknown;
|
|
140
|
+
} | {
|
|
141
|
+
ok: false;
|
|
142
|
+
status: number;
|
|
143
|
+
error: string;
|
|
144
|
+
};
|
|
100
145
|
export {};
|
|
101
146
|
//# sourceMappingURL=apps-api.d.ts.map
|
package/dist/apps-api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apps-api.d.ts","sourceRoot":"","sources":["../src/apps-api.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,EAQL,KAAK,WAAW,EAGjB,MAAM,0BAA0B,CAAC;AASlC,UAAU,GAAG;IACX,GAAG,EAAE,eAAe,CAAC;IACrB,GAAG,EAAE,cAAc,CAAC;IACpB,GAAG,EAAE,GAAG,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"apps-api.d.ts","sourceRoot":"","sources":["../src/apps-api.ts"],"names":[],"mappings":"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"}
|
package/dist/apps-api.js
CHANGED
|
@@ -83,6 +83,73 @@ export async function handleAppsApi(ctx) {
|
|
|
83
83
|
}
|
|
84
84
|
return true;
|
|
85
85
|
}
|
|
86
|
+
m = path.match(/^\/api\/apps\/([^/]+)\/notebook-candidates$/);
|
|
87
|
+
if (m && req.method === 'GET') {
|
|
88
|
+
const appId = decodeURIComponent(m[1]);
|
|
89
|
+
const loaded = loadAppById(projectRoot, appId);
|
|
90
|
+
if (!loaded) {
|
|
91
|
+
sendJson(res, 404, { error: `App "${appId}" not found` });
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
sendJson(res, 200, { notebooks: listNotebookCandidates(projectRoot, loaded.app, join(projectRoot, 'apps', appId)) });
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
m = path.match(/^\/api\/apps\/([^/]+)\/notebooks\/create$/);
|
|
98
|
+
if (m && req.method === 'POST') {
|
|
99
|
+
const appId = decodeURIComponent(m[1]);
|
|
100
|
+
try {
|
|
101
|
+
const body = await readJson(req);
|
|
102
|
+
const result = createNotebookForApp(projectRoot, appId, body);
|
|
103
|
+
if (!result.ok) {
|
|
104
|
+
sendJson(res, 400, { error: result.error });
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
sendJson(res, 201, result);
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
sendJson(res, 500, { error: err.message });
|
|
111
|
+
}
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
m = path.match(/^\/api\/apps\/([^/]+)\/notebooks\/preview$/);
|
|
115
|
+
if (m && req.method === 'GET') {
|
|
116
|
+
const appId = decodeURIComponent(m[1]);
|
|
117
|
+
const notebookPath = ctx.url.searchParams.get('path') ?? '';
|
|
118
|
+
const result = previewNotebookForApp(projectRoot, appId, notebookPath);
|
|
119
|
+
if (!result.ok) {
|
|
120
|
+
sendJson(res, result.status, { error: result.error });
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
sendJson(res, 200, result.preview);
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
m = path.match(/^\/api\/apps\/([^/]+)\/notebooks\/run$/);
|
|
127
|
+
if (m && req.method === 'POST') {
|
|
128
|
+
const appId = decodeURIComponent(m[1]);
|
|
129
|
+
try {
|
|
130
|
+
const body = await readJson(req);
|
|
131
|
+
const notebookPath = cleanString(body.path);
|
|
132
|
+
if (!notebookPath) {
|
|
133
|
+
sendJson(res, 400, { error: 'path is required' });
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
if (!ctx.runNotebook) {
|
|
137
|
+
sendJson(res, 400, { error: 'Notebook run is unavailable in this host.' });
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
await ctx.runNotebook(appId, notebookPath);
|
|
141
|
+
const preview = previewNotebookForApp(projectRoot, appId, notebookPath);
|
|
142
|
+
if (!preview.ok) {
|
|
143
|
+
sendJson(res, preview.status, { error: preview.error });
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
sendJson(res, 200, { ok: true, preview: preview.preview });
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
sendJson(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
150
|
+
}
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
86
153
|
m = path.match(/^\/api\/apps\/([^/]+)\/notebooks$/);
|
|
87
154
|
if (m && req.method === 'POST') {
|
|
88
155
|
const appId = decodeURIComponent(m[1]);
|
|
@@ -100,6 +167,79 @@ export async function handleAppsApi(ctx) {
|
|
|
100
167
|
}
|
|
101
168
|
return true;
|
|
102
169
|
}
|
|
170
|
+
m = path.match(/^\/api\/apps\/([^/]+)\/conversations$/);
|
|
171
|
+
if (m) {
|
|
172
|
+
const appId = decodeURIComponent(m[1]);
|
|
173
|
+
if (!loadAppById(projectRoot, appId)) {
|
|
174
|
+
sendJson(res, 404, { error: `App "${appId}" not found` });
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
const storage = new LocalAppStorage(defaultLocalAppsDbPath(projectRoot));
|
|
178
|
+
try {
|
|
179
|
+
if (req.method === 'GET') {
|
|
180
|
+
sendJson(res, 200, { conversations: storage.listAppConversations(appId) });
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
if (req.method === 'POST') {
|
|
184
|
+
const body = await readJson(req);
|
|
185
|
+
const conversation = storage.createAppConversation({
|
|
186
|
+
appId,
|
|
187
|
+
title: body.title,
|
|
188
|
+
dashboardId: body.dashboardId,
|
|
189
|
+
notebookPath: body.notebookPath,
|
|
190
|
+
messages: normalizeConversationMessages(body.messages),
|
|
191
|
+
});
|
|
192
|
+
sendJson(res, 201, { ok: true, conversation });
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
sendJson(res, 500, { error: err.message });
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
storage.close();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
m = path.match(/^\/api\/apps\/([^/]+)\/conversations\/([^/]+)$/);
|
|
205
|
+
if (m) {
|
|
206
|
+
const appId = decodeURIComponent(m[1]);
|
|
207
|
+
const conversationId = decodeURIComponent(m[2]);
|
|
208
|
+
const storage = new LocalAppStorage(defaultLocalAppsDbPath(projectRoot));
|
|
209
|
+
try {
|
|
210
|
+
const conversation = storage.getAppConversation(conversationId);
|
|
211
|
+
if (!conversation || conversation.appId !== appId) {
|
|
212
|
+
sendJson(res, 404, { error: `Conversation "${conversationId}" not found` });
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
if (req.method === 'GET') {
|
|
216
|
+
sendJson(res, 200, { conversation });
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
if (req.method === 'PATCH') {
|
|
220
|
+
const body = await readJson(req);
|
|
221
|
+
const updated = storage.updateAppConversation(conversationId, {
|
|
222
|
+
title: body.title,
|
|
223
|
+
dashboardId: body.dashboardId,
|
|
224
|
+
notebookPath: body.notebookPath,
|
|
225
|
+
messages: body.messages ? normalizeConversationMessages(body.messages) : undefined,
|
|
226
|
+
});
|
|
227
|
+
sendJson(res, 200, { ok: true, conversation: updated });
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
if (req.method === 'DELETE') {
|
|
231
|
+
sendJson(res, 200, { ok: storage.deleteAppConversation(conversationId) });
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
catch (err) {
|
|
236
|
+
sendJson(res, 500, { error: err.message });
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
finally {
|
|
240
|
+
storage.close();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
103
243
|
m = path.match(/^\/api\/apps\/([^/]+)\/ai-pins$/);
|
|
104
244
|
if (m) {
|
|
105
245
|
const appId = decodeURIComponent(m[1]);
|
|
@@ -388,6 +528,8 @@ export function createAppPackage(projectRoot, input) {
|
|
|
388
528
|
const appDir = join(projectRoot, 'apps', id);
|
|
389
529
|
if (existsSync(appDir))
|
|
390
530
|
return { ok: false, error: `App already exists: ${id}` };
|
|
531
|
+
const dashboardTitle = cleanString(input.dashboardTitle) || 'Overview';
|
|
532
|
+
const dashboardId = slugify(dashboardTitle) || 'overview';
|
|
391
533
|
const owner = cleanString(input.owners?.[0]) || `${process.env.USER ?? 'owner'}@local`;
|
|
392
534
|
const audience = cleanString(input.audience);
|
|
393
535
|
const subdomain = cleanString(input.subdomain);
|
|
@@ -451,13 +593,13 @@ export function createAppPackage(projectRoot, input) {
|
|
|
451
593
|
],
|
|
452
594
|
rlsBindings: [],
|
|
453
595
|
schedules: [],
|
|
454
|
-
homepage: { type: 'dashboard', id:
|
|
596
|
+
homepage: { type: 'dashboard', id: dashboardId },
|
|
455
597
|
};
|
|
456
598
|
const dashboard = {
|
|
457
599
|
version: 1,
|
|
458
|
-
id:
|
|
600
|
+
id: dashboardId,
|
|
459
601
|
metadata: {
|
|
460
|
-
title:
|
|
602
|
+
title: dashboardTitle,
|
|
461
603
|
description: cleanString(input.purpose) || `Starter dashboard for ${name}`,
|
|
462
604
|
domain,
|
|
463
605
|
subdomain: subdomain || undefined,
|
|
@@ -477,7 +619,7 @@ export function createAppPackage(projectRoot, input) {
|
|
|
477
619
|
const paths = [
|
|
478
620
|
join(appDir, 'dql.app.json'),
|
|
479
621
|
join(appDir, 'README.md'),
|
|
480
|
-
join(appDir, 'dashboards',
|
|
622
|
+
join(appDir, 'dashboards', `${dashboardId}.dqld`),
|
|
481
623
|
join(appDir, 'notebooks'),
|
|
482
624
|
join(appDir, 'drafts'),
|
|
483
625
|
];
|
|
@@ -485,8 +627,8 @@ export function createAppPackage(projectRoot, input) {
|
|
|
485
627
|
mkdirSync(join(appDir, 'notebooks'), { recursive: true });
|
|
486
628
|
mkdirSync(join(appDir, 'drafts'), { recursive: true });
|
|
487
629
|
writeFileSync(join(appDir, 'dql.app.json'), JSON.stringify(app, null, 2) + '\n', 'utf-8');
|
|
488
|
-
writeFileSync(join(appDir, 'dashboards',
|
|
489
|
-
writeFileSync(join(appDir, 'README.md'), appReadme(app, audience, selectedBlocks), 'utf-8');
|
|
630
|
+
writeFileSync(join(appDir, 'dashboards', `${dashboardId}.dqld`), JSON.stringify(dashboard, null, 2) + '\n', 'utf-8');
|
|
631
|
+
writeFileSync(join(appDir, 'README.md'), appReadme(app, audience, selectedBlocks, dashboardId), 'utf-8');
|
|
490
632
|
const created = collectAppsList(projectRoot).find((entry) => entry.id === id);
|
|
491
633
|
if (!created)
|
|
492
634
|
return { ok: false, error: `App was written but could not be reloaded: ${id}` };
|
|
@@ -494,7 +636,7 @@ export function createAppPackage(projectRoot, input) {
|
|
|
494
636
|
ok: true,
|
|
495
637
|
app: created,
|
|
496
638
|
paths: paths.map((path) => path.startsWith(projectRoot) ? path.slice(projectRoot.length + 1) : path),
|
|
497
|
-
dashboardId
|
|
639
|
+
dashboardId,
|
|
498
640
|
};
|
|
499
641
|
}
|
|
500
642
|
function buildDashboardItems(blocks) {
|
|
@@ -584,7 +726,7 @@ function normalizeVizType(chartType) {
|
|
|
584
726
|
return 'funnel';
|
|
585
727
|
return 'table';
|
|
586
728
|
}
|
|
587
|
-
function appReadme(app, audience, blocks) {
|
|
729
|
+
function appReadme(app, audience, blocks, dashboardId = 'overview') {
|
|
588
730
|
return [
|
|
589
731
|
`# ${app.name}`,
|
|
590
732
|
'',
|
|
@@ -597,7 +739,7 @@ function appReadme(app, audience, blocks) {
|
|
|
597
739
|
`- Visibility: ${app.visibility}`,
|
|
598
740
|
`- Lifecycle: ${app.lifecycle}`,
|
|
599
741
|
`- Owners: ${app.owners.join(', ')}`,
|
|
600
|
-
`- Starter dashboard: dashboards
|
|
742
|
+
`- Starter dashboard: dashboards/${dashboardId}.dqld`,
|
|
601
743
|
`- Supporting notebooks: notebooks/`,
|
|
602
744
|
`- Draft blocks: drafts/`,
|
|
603
745
|
'',
|
|
@@ -684,6 +826,17 @@ function matchArray(source, regex) {
|
|
|
684
826
|
.map((item) => item.trim().replace(/^"|"$/g, ''))
|
|
685
827
|
.filter(Boolean);
|
|
686
828
|
}
|
|
829
|
+
function normalizeConversationMessages(messages) {
|
|
830
|
+
return (messages ?? [])
|
|
831
|
+
.map((message) => ({
|
|
832
|
+
id: cleanString(message.id) || undefined,
|
|
833
|
+
role: message.role === 'assistant' ? 'assistant' : 'user',
|
|
834
|
+
content: cleanString(message.content),
|
|
835
|
+
events: Array.isArray(message.events) ? message.events : [],
|
|
836
|
+
createdAt: cleanString(message.createdAt) || undefined,
|
|
837
|
+
}))
|
|
838
|
+
.filter((message) => message.content.length > 0);
|
|
839
|
+
}
|
|
687
840
|
function cleanString(value) {
|
|
688
841
|
return typeof value === 'string' ? value.trim() : '';
|
|
689
842
|
}
|
|
@@ -726,8 +879,8 @@ function createDashboardForApp(projectRoot, appId, input) {
|
|
|
726
879
|
const loaded = loadAppById(projectRoot, appId);
|
|
727
880
|
if (!loaded)
|
|
728
881
|
return { ok: false, error: `App "${appId}" not found` };
|
|
729
|
-
const title = cleanString(input.title) || 'New
|
|
730
|
-
const id = slugify(cleanString(input.id) || title) || `
|
|
882
|
+
const title = cleanString(input.title) || 'New page';
|
|
883
|
+
const id = slugify(cleanString(input.id) || title) || `page-${Date.now()}`;
|
|
731
884
|
if (!/^[a-z0-9][a-z0-9_-]*$/i.test(id))
|
|
732
885
|
return { ok: false, error: 'dashboard id must be folder-safe' };
|
|
733
886
|
const appDir = join(projectRoot, 'apps', appId);
|
|
@@ -739,7 +892,7 @@ function createDashboardForApp(projectRoot, appId, input) {
|
|
|
739
892
|
id,
|
|
740
893
|
metadata: {
|
|
741
894
|
title,
|
|
742
|
-
description: cleanString(input.description) || `${title} dashboard
|
|
895
|
+
description: cleanString(input.description) || `${title} dashboard page`,
|
|
743
896
|
domain: loaded.app.domain,
|
|
744
897
|
subdomain: loaded.app.subdomain,
|
|
745
898
|
groups: loaded.app.groups ?? [],
|
|
@@ -925,6 +1078,120 @@ function loadAppById(projectRoot, id) {
|
|
|
925
1078
|
}
|
|
926
1079
|
return null;
|
|
927
1080
|
}
|
|
1081
|
+
export function listNotebookCandidates(projectRoot, app, appDir) {
|
|
1082
|
+
const attached = new Map(listAppNotebookRefs(projectRoot, app, appDir).map((notebook) => [notebook.path, notebook]));
|
|
1083
|
+
const files = new Map();
|
|
1084
|
+
for (const root of ['notebooks', 'workbooks', 'apps']) {
|
|
1085
|
+
for (const file of scanFiles(join(projectRoot, root), '.dqlnb')) {
|
|
1086
|
+
const rel = relative(projectRoot, file).replaceAll('\\', '/');
|
|
1087
|
+
files.set(rel, file);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
for (const notebook of attached.values()) {
|
|
1091
|
+
const abs = join(projectRoot, notebook.path);
|
|
1092
|
+
if (existsSync(abs))
|
|
1093
|
+
files.set(notebook.path, abs);
|
|
1094
|
+
}
|
|
1095
|
+
return Array.from(files.entries())
|
|
1096
|
+
.map(([path, abs]) => {
|
|
1097
|
+
const ref = attached.get(path);
|
|
1098
|
+
const stat = statSyncSafe(abs);
|
|
1099
|
+
return {
|
|
1100
|
+
path,
|
|
1101
|
+
title: ref?.title ?? notebookTitleFromFile(abs) ?? titleFromPath(path),
|
|
1102
|
+
attached: Boolean(ref),
|
|
1103
|
+
role: ref?.role,
|
|
1104
|
+
visibility: ref?.visibility,
|
|
1105
|
+
lastModified: stat?.mtime.toISOString(),
|
|
1106
|
+
};
|
|
1107
|
+
})
|
|
1108
|
+
.sort((a, b) => Number(b.attached) - Number(a.attached) || a.title.localeCompare(b.title));
|
|
1109
|
+
}
|
|
1110
|
+
export function createNotebookForApp(projectRoot, appId, input) {
|
|
1111
|
+
const loaded = loadAppById(projectRoot, appId);
|
|
1112
|
+
if (!loaded)
|
|
1113
|
+
return { ok: false, error: `App "${appId}" not found` };
|
|
1114
|
+
const title = cleanString(input.title) || cleanString(input.name) || 'App analysis';
|
|
1115
|
+
const slug = slugify(cleanString(input.name) || title) || `notebook-${Date.now()}`;
|
|
1116
|
+
const appDir = join(projectRoot, 'apps', appId);
|
|
1117
|
+
const relPath = `apps/${appId}/notebooks/${slug}.dqlnb`;
|
|
1118
|
+
const absPath = join(projectRoot, relPath);
|
|
1119
|
+
if (existsSync(absPath))
|
|
1120
|
+
return { ok: false, error: `Notebook already exists: ${relPath}` };
|
|
1121
|
+
mkdirSync(dirname(absPath), { recursive: true });
|
|
1122
|
+
writeFileSync(absPath, buildAppNotebookTemplate(title, loaded.app, input.template), 'utf-8');
|
|
1123
|
+
const attached = attachNotebookToApp(projectRoot, appId, {
|
|
1124
|
+
path: relPath,
|
|
1125
|
+
title,
|
|
1126
|
+
role: normalizeNotebookRole(input.role),
|
|
1127
|
+
visibility: input.visibility,
|
|
1128
|
+
});
|
|
1129
|
+
if (!attached.ok)
|
|
1130
|
+
return { ok: false, error: attached.error };
|
|
1131
|
+
const preview = previewNotebookForApp(projectRoot, appId, relPath);
|
|
1132
|
+
return {
|
|
1133
|
+
ok: true,
|
|
1134
|
+
path: relPath,
|
|
1135
|
+
app: loadAppById(projectRoot, appId),
|
|
1136
|
+
preview: preview.ok ? preview.preview : null,
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
export function previewNotebookForApp(projectRoot, appId, notebookPath) {
|
|
1140
|
+
if (!loadAppById(projectRoot, appId))
|
|
1141
|
+
return { ok: false, status: 404, error: `App "${appId}" not found` };
|
|
1142
|
+
const rel = cleanString(notebookPath).replaceAll('\\', '/');
|
|
1143
|
+
if (!rel || rel.startsWith('/') || rel.includes('..') || !rel.endsWith('.dqlnb')) {
|
|
1144
|
+
return { ok: false, status: 400, error: 'notebook path must be a project-relative .dqlnb path' };
|
|
1145
|
+
}
|
|
1146
|
+
const abs = join(projectRoot, rel);
|
|
1147
|
+
if (!existsSync(abs))
|
|
1148
|
+
return { ok: false, status: 404, error: `Notebook not found: ${rel}` };
|
|
1149
|
+
try {
|
|
1150
|
+
const raw = readFileSync(abs, 'utf-8');
|
|
1151
|
+
const parsed = JSON.parse(raw);
|
|
1152
|
+
const snapshot = readNotebookRunSnapshot(abs);
|
|
1153
|
+
const snapshotByCell = new Map();
|
|
1154
|
+
for (const entry of snapshot?.cells ?? []) {
|
|
1155
|
+
if (entry && typeof entry === 'object' && typeof entry.cellId === 'string') {
|
|
1156
|
+
snapshotByCell.set(String(entry.cellId), entry);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
const cells = (parsed.cells ?? []).map((cell, index) => {
|
|
1160
|
+
const id = typeof cell.id === 'string' ? cell.id : `cell-${index + 1}`;
|
|
1161
|
+
const snap = snapshotByCell.get(id);
|
|
1162
|
+
return {
|
|
1163
|
+
id,
|
|
1164
|
+
type: typeof cell.type === 'string' ? cell.type : 'sql',
|
|
1165
|
+
name: typeof cell.name === 'string' ? cell.name : typeof cell.title === 'string' ? cell.title : undefined,
|
|
1166
|
+
content: typeof cell.content === 'string' ? cell.content : typeof cell.source === 'string' ? cell.source : '',
|
|
1167
|
+
upstream: typeof cell.upstream === 'string' ? cell.upstream : undefined,
|
|
1168
|
+
chartConfig: cell.chartConfig ?? cell.config,
|
|
1169
|
+
tableConfig: cell.tableConfig,
|
|
1170
|
+
singleValueConfig: cell.singleValueConfig,
|
|
1171
|
+
pivotConfig: cell.pivotConfig,
|
|
1172
|
+
status: snap?.status ?? 'idle',
|
|
1173
|
+
result: snap?.result,
|
|
1174
|
+
error: snap?.error,
|
|
1175
|
+
executionCount: snap?.executionCount,
|
|
1176
|
+
executedAt: snap?.executedAt,
|
|
1177
|
+
};
|
|
1178
|
+
});
|
|
1179
|
+
return {
|
|
1180
|
+
ok: true,
|
|
1181
|
+
preview: {
|
|
1182
|
+
path: rel,
|
|
1183
|
+
title: parsed.title ?? parsed.metadata?.title ?? titleFromPath(rel),
|
|
1184
|
+
metadata: parsed.metadata ?? {},
|
|
1185
|
+
cells,
|
|
1186
|
+
snapshotFound: Boolean(snapshot),
|
|
1187
|
+
capturedAt: typeof snapshot?.capturedAt === 'string' ? snapshot.capturedAt : undefined,
|
|
1188
|
+
},
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
catch (err) {
|
|
1192
|
+
return { ok: false, status: 400, error: err instanceof Error ? err.message : String(err) };
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
928
1195
|
function attachNotebookToApp(projectRoot, appId, input) {
|
|
929
1196
|
const notebookPath = cleanString(input.path).replaceAll('\\', '/');
|
|
930
1197
|
if (!notebookPath)
|
|
@@ -985,6 +1252,78 @@ function listAppNotebookRefs(projectRoot, app, appDir) {
|
|
|
985
1252
|
}
|
|
986
1253
|
return Array.from(byPath.values()).sort((a, b) => (a.title ?? a.path).localeCompare(b.title ?? b.path));
|
|
987
1254
|
}
|
|
1255
|
+
function buildAppNotebookTemplate(title, app, template) {
|
|
1256
|
+
const normalizedTemplate = cleanString(template) || 'blank';
|
|
1257
|
+
const cellId = (base) => `${slugify(base) || 'cell'}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1258
|
+
const intro = [
|
|
1259
|
+
`# ${title}`,
|
|
1260
|
+
'',
|
|
1261
|
+
`App: ${app.name}`,
|
|
1262
|
+
`Domain: ${[app.domain, app.subdomain, ...(app.groups ?? [])].filter(Boolean).join(' / ')}`,
|
|
1263
|
+
'',
|
|
1264
|
+
'Use this notebook for analysis that supports the App dashboard pages.',
|
|
1265
|
+
].join('\n');
|
|
1266
|
+
const cells = [
|
|
1267
|
+
{
|
|
1268
|
+
id: cellId('intro'),
|
|
1269
|
+
type: 'markdown',
|
|
1270
|
+
content: intro,
|
|
1271
|
+
},
|
|
1272
|
+
{
|
|
1273
|
+
id: cellId('starter-sql'),
|
|
1274
|
+
type: 'sql',
|
|
1275
|
+
name: 'starter_query',
|
|
1276
|
+
content: '-- Write supporting SQL for this App here\nSELECT 1 AS value;',
|
|
1277
|
+
},
|
|
1278
|
+
];
|
|
1279
|
+
if (normalizedTemplate === 'summary') {
|
|
1280
|
+
cells.push({
|
|
1281
|
+
id: cellId('summary'),
|
|
1282
|
+
type: 'markdown',
|
|
1283
|
+
content: '## Notes\n\nAdd observations, assumptions, and follow-up questions here.',
|
|
1284
|
+
});
|
|
1285
|
+
}
|
|
1286
|
+
return JSON.stringify({
|
|
1287
|
+
dqlnbVersion: 1,
|
|
1288
|
+
version: 1,
|
|
1289
|
+
title,
|
|
1290
|
+
metadata: {
|
|
1291
|
+
description: `Supporting notebook for ${app.name}`,
|
|
1292
|
+
status: 'draft',
|
|
1293
|
+
categories: [app.domain, app.subdomain, ...(app.groups ?? [])].filter(Boolean),
|
|
1294
|
+
createdAt: new Date().toISOString(),
|
|
1295
|
+
modifiedAt: new Date().toISOString(),
|
|
1296
|
+
},
|
|
1297
|
+
cells,
|
|
1298
|
+
}, null, 2) + '\n';
|
|
1299
|
+
}
|
|
1300
|
+
function notebookTitleFromFile(absPath) {
|
|
1301
|
+
try {
|
|
1302
|
+
const parsed = JSON.parse(readFileSync(absPath, 'utf-8'));
|
|
1303
|
+
if (typeof parsed.title === 'string' && parsed.title.trim())
|
|
1304
|
+
return parsed.title.trim();
|
|
1305
|
+
if (typeof parsed.metadata?.title === 'string' && parsed.metadata.title.trim())
|
|
1306
|
+
return parsed.metadata.title.trim();
|
|
1307
|
+
}
|
|
1308
|
+
catch {
|
|
1309
|
+
// fall back to path-derived title
|
|
1310
|
+
}
|
|
1311
|
+
return null;
|
|
1312
|
+
}
|
|
1313
|
+
function readNotebookRunSnapshot(absNotebookPath) {
|
|
1314
|
+
const snapshotPath = absNotebookPath.replace(/\.dqlnb$/i, '.run.json');
|
|
1315
|
+
if (!existsSync(snapshotPath))
|
|
1316
|
+
return null;
|
|
1317
|
+
try {
|
|
1318
|
+
return JSON.parse(readFileSync(snapshotPath, 'utf-8'));
|
|
1319
|
+
}
|
|
1320
|
+
catch {
|
|
1321
|
+
return null;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
function normalizeNotebookRole(value) {
|
|
1325
|
+
return value === 'source' || value === 'analysis' ? value : 'supporting';
|
|
1326
|
+
}
|
|
988
1327
|
function listAppDrafts(projectRoot, appDir) {
|
|
989
1328
|
return scanFiles(join(appDir, 'drafts'), '.dql').map((file) => {
|
|
990
1329
|
const source = readFileSync(file, 'utf-8');
|