@duckcodeailabs/dql-cli 1.3.2 → 1.3.4
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/assets/dql-notebook/assets/{codemirror-BqWuFwtC.js → codemirror-DJYUkPr1.js} +10 -10
- package/dist/assets/dql-notebook/assets/index-BfBn8l8M.css +1 -0
- package/dist/assets/dql-notebook/assets/index-CudxJ9DW.js +847 -0
- package/dist/assets/dql-notebook/index.html +5 -5
- package/dist/local-runtime.d.ts.map +1 -1
- package/dist/local-runtime.js +250 -4
- package/dist/local-runtime.js.map +1 -1
- package/package.json +8 -8
- package/dist/assets/dql-notebook/assets/index-BOO4KPgS.js +0 -793
- package/dist/assets/dql-notebook/assets/index-BlLkkJvL.css +0 -1
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
2
|
+
<html lang="en" data-theme="paper">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>DQL Notebook</title>
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
9
|
-
<link href="https://fonts.googleapis.com/css2?family=
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600;700&family=Kalam:wght@400;700&family=Caveat:wght@400;600;700&display=swap" rel="stylesheet" />
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-CudxJ9DW.js"></script>
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/react-CRB3T2We.js">
|
|
12
|
-
<link rel="modulepreload" crossorigin href="/assets/codemirror-
|
|
13
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
12
|
+
<link rel="modulepreload" crossorigin href="/assets/codemirror-DJYUkPr1.js">
|
|
13
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BfBn8l8M.css">
|
|
14
14
|
</head>
|
|
15
15
|
<body>
|
|
16
16
|
<div id="root"></div>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-runtime.d.ts","sourceRoot":"","sources":["../src/local-runtime.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,KAAK,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAUtF,OAAO,EAcL,KAAK,aAAa,EAClB,KAAK,2BAA2B,EAUjC,MAAM,0BAA0B,CAAC;AAclC,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iBAAiB,CAAC,EAAE,gBAAgB,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,2BAA2B,CAAC;IAC5C,OAAO,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB,CAAC;CACH;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,aAAa,CAAC;IACxB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"local-runtime.d.ts","sourceRoot":"","sources":["../src/local-runtime.ts"],"names":[],"mappings":"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"}
|
package/dist/local-runtime.js
CHANGED
|
@@ -282,8 +282,121 @@ export async function startLocalServer(opts) {
|
|
|
282
282
|
}
|
|
283
283
|
if (req.method === 'GET' && path === '/api/git/diff') {
|
|
284
284
|
const filePath = url.searchParams.get('path') ?? '';
|
|
285
|
+
const staged = url.searchParams.get('staged') === 'true';
|
|
285
286
|
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
286
|
-
res.end(serializeJSON(await readGitDiff(projectRoot, filePath)));
|
|
287
|
+
res.end(serializeJSON(await readGitDiff(projectRoot, filePath, staged)));
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (req.method === 'GET' && path === '/api/git/branches') {
|
|
291
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
292
|
+
res.end(serializeJSON(await readGitBranches(projectRoot)));
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (req.method === 'GET' && path === '/api/git/remote') {
|
|
296
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
297
|
+
res.end(serializeJSON(await readGitRemote(projectRoot)));
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (req.method === 'POST' && path === '/api/git/stage') {
|
|
301
|
+
try {
|
|
302
|
+
const body = (await readJSON(req));
|
|
303
|
+
const result = await gitStage(projectRoot, body.paths ?? []);
|
|
304
|
+
res.writeHead(result.ok ? 200 : 400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
305
|
+
res.end(serializeJSON(result));
|
|
306
|
+
}
|
|
307
|
+
catch (e) {
|
|
308
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
309
|
+
res.end(serializeJSON({ ok: false, error: e instanceof Error ? e.message : String(e) }));
|
|
310
|
+
}
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
if (req.method === 'POST' && path === '/api/git/unstage') {
|
|
314
|
+
try {
|
|
315
|
+
const body = (await readJSON(req));
|
|
316
|
+
const result = await gitUnstage(projectRoot, body.paths ?? []);
|
|
317
|
+
res.writeHead(result.ok ? 200 : 400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
318
|
+
res.end(serializeJSON(result));
|
|
319
|
+
}
|
|
320
|
+
catch (e) {
|
|
321
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
322
|
+
res.end(serializeJSON({ ok: false, error: e instanceof Error ? e.message : String(e) }));
|
|
323
|
+
}
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (req.method === 'POST' && path === '/api/git/discard') {
|
|
327
|
+
try {
|
|
328
|
+
const body = (await readJSON(req));
|
|
329
|
+
const result = await gitDiscard(projectRoot, body.paths ?? []);
|
|
330
|
+
res.writeHead(result.ok ? 200 : 400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
331
|
+
res.end(serializeJSON(result));
|
|
332
|
+
}
|
|
333
|
+
catch (e) {
|
|
334
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
335
|
+
res.end(serializeJSON({ ok: false, error: e instanceof Error ? e.message : String(e) }));
|
|
336
|
+
}
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
if (req.method === 'POST' && path === '/api/git/commit') {
|
|
340
|
+
try {
|
|
341
|
+
const body = (await readJSON(req));
|
|
342
|
+
const result = await gitCommit(projectRoot, body.message ?? '', body.stageAll === true);
|
|
343
|
+
res.writeHead(result.ok ? 200 : 400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
344
|
+
res.end(serializeJSON(result));
|
|
345
|
+
}
|
|
346
|
+
catch (e) {
|
|
347
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
348
|
+
res.end(serializeJSON({ ok: false, error: e instanceof Error ? e.message : String(e) }));
|
|
349
|
+
}
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (req.method === 'POST' && path === '/api/git/push') {
|
|
353
|
+
try {
|
|
354
|
+
const result = await gitPush(projectRoot);
|
|
355
|
+
res.writeHead(result.ok ? 200 : 400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
356
|
+
res.end(serializeJSON(result));
|
|
357
|
+
}
|
|
358
|
+
catch (e) {
|
|
359
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
360
|
+
res.end(serializeJSON({ ok: false, error: e instanceof Error ? e.message : String(e) }));
|
|
361
|
+
}
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (req.method === 'POST' && path === '/api/git/pull') {
|
|
365
|
+
try {
|
|
366
|
+
const result = await gitPull(projectRoot);
|
|
367
|
+
res.writeHead(result.ok ? 200 : 400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
368
|
+
res.end(serializeJSON(result));
|
|
369
|
+
}
|
|
370
|
+
catch (e) {
|
|
371
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
372
|
+
res.end(serializeJSON({ ok: false, error: e instanceof Error ? e.message : String(e) }));
|
|
373
|
+
}
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
if (req.method === 'POST' && path === '/api/git/branch') {
|
|
377
|
+
try {
|
|
378
|
+
const body = (await readJSON(req));
|
|
379
|
+
const result = await gitCreateBranch(projectRoot, body.name ?? '', body.checkout !== false);
|
|
380
|
+
res.writeHead(result.ok ? 200 : 400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
381
|
+
res.end(serializeJSON(result));
|
|
382
|
+
}
|
|
383
|
+
catch (e) {
|
|
384
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
385
|
+
res.end(serializeJSON({ ok: false, error: e instanceof Error ? e.message : String(e) }));
|
|
386
|
+
}
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (req.method === 'POST' && path === '/api/git/checkout') {
|
|
390
|
+
try {
|
|
391
|
+
const body = (await readJSON(req));
|
|
392
|
+
const result = await gitCheckout(projectRoot, body.name ?? '');
|
|
393
|
+
res.writeHead(result.ok ? 200 : 400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
394
|
+
res.end(serializeJSON(result));
|
|
395
|
+
}
|
|
396
|
+
catch (e) {
|
|
397
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
398
|
+
res.end(serializeJSON({ ok: false, error: e instanceof Error ? e.message : String(e) }));
|
|
399
|
+
}
|
|
287
400
|
return;
|
|
288
401
|
}
|
|
289
402
|
if (req.method === 'GET' && path === '/api/schema') {
|
|
@@ -3523,24 +3636,157 @@ function ensureGitignoreEntry(projectRoot, pattern) {
|
|
|
3523
3636
|
// Best-effort; failure to write .gitignore shouldn't fail the snapshot.
|
|
3524
3637
|
}
|
|
3525
3638
|
}
|
|
3526
|
-
async function readGitDiff(cwd, filePath) {
|
|
3639
|
+
async function readGitDiff(cwd, filePath, staged = false) {
|
|
3527
3640
|
const isRepo = await execGit(cwd, ['rev-parse', '--is-inside-work-tree']);
|
|
3528
3641
|
if (isRepo.code !== 0) {
|
|
3529
3642
|
return { inRepo: false, diff: '', before: null, after: null, diffReport: null };
|
|
3530
3643
|
}
|
|
3644
|
+
const baseArgs = staged ? ['diff', '--cached', '--no-color'] : ['diff', '--no-color'];
|
|
3531
3645
|
if (!filePath) {
|
|
3532
|
-
const res = await execGit(cwd,
|
|
3646
|
+
const res = await execGit(cwd, baseArgs);
|
|
3533
3647
|
return { inRepo: true, diff: res.stdout, before: null, after: null, diffReport: null };
|
|
3534
3648
|
}
|
|
3535
3649
|
const isSemantic = filePath.endsWith('.dql') || filePath.endsWith('.dqlnb');
|
|
3536
3650
|
const [diffRes, before, after] = await Promise.all([
|
|
3537
|
-
execGit(cwd, [
|
|
3651
|
+
execGit(cwd, [...baseArgs, '--', filePath]),
|
|
3538
3652
|
isSemantic ? readHeadBlob(cwd, filePath) : Promise.resolve(null),
|
|
3539
3653
|
isSemantic ? readWorkingCopy(join(cwd, filePath)) : Promise.resolve(null),
|
|
3540
3654
|
]);
|
|
3541
3655
|
const diffReport = isSemantic ? computeSemanticDiff(filePath, before, after) : null;
|
|
3542
3656
|
return { inRepo: true, diff: diffRes.stdout, before, after, diffReport };
|
|
3543
3657
|
}
|
|
3658
|
+
// ── git write operations ──────────────────────────────────────────────────
|
|
3659
|
+
// Each helper validates inputs, shells out via execFile (no shell expansion),
|
|
3660
|
+
// then reports the trimmed stderr on failure so the UI can surface it. We
|
|
3661
|
+
// never accept absolute paths or paths containing `..` — staged paths must
|
|
3662
|
+
// stay inside the project root.
|
|
3663
|
+
function validatePaths(cwd, paths) {
|
|
3664
|
+
if (!Array.isArray(paths) || paths.length === 0) {
|
|
3665
|
+
return { ok: false, error: 'No paths provided' };
|
|
3666
|
+
}
|
|
3667
|
+
const cleaned = [];
|
|
3668
|
+
for (const p of paths) {
|
|
3669
|
+
if (typeof p !== 'string' || p.length === 0)
|
|
3670
|
+
return { ok: false, error: 'Invalid path' };
|
|
3671
|
+
if (p.startsWith('/'))
|
|
3672
|
+
return { ok: false, error: `Absolute path not allowed: ${p}` };
|
|
3673
|
+
if (p.split('/').includes('..'))
|
|
3674
|
+
return { ok: false, error: `Path escape not allowed: ${p}` };
|
|
3675
|
+
const resolved = join(cwd, p);
|
|
3676
|
+
if (!resolved.startsWith(cwd))
|
|
3677
|
+
return { ok: false, error: `Path outside project: ${p}` };
|
|
3678
|
+
cleaned.push(p);
|
|
3679
|
+
}
|
|
3680
|
+
return { ok: true, paths: cleaned };
|
|
3681
|
+
}
|
|
3682
|
+
function gitErrorOutput(res) {
|
|
3683
|
+
return (res.stderr || res.stdout || '').trim();
|
|
3684
|
+
}
|
|
3685
|
+
async function gitStage(cwd, paths) {
|
|
3686
|
+
const v = validatePaths(cwd, paths);
|
|
3687
|
+
if (!v.ok)
|
|
3688
|
+
return { ok: false, error: v.error };
|
|
3689
|
+
const res = await execGit(cwd, ['add', '--', ...v.paths]);
|
|
3690
|
+
return res.code === 0 ? { ok: true } : { ok: false, error: gitErrorOutput(res) };
|
|
3691
|
+
}
|
|
3692
|
+
async function gitUnstage(cwd, paths) {
|
|
3693
|
+
const v = validatePaths(cwd, paths);
|
|
3694
|
+
if (!v.ok)
|
|
3695
|
+
return { ok: false, error: v.error };
|
|
3696
|
+
// `restore --staged` works with or without HEAD; for an initial commit (no
|
|
3697
|
+
// HEAD yet) git's `rm --cached` is the fallback. Try restore first.
|
|
3698
|
+
const res = await execGit(cwd, ['restore', '--staged', '--', ...v.paths]);
|
|
3699
|
+
if (res.code === 0)
|
|
3700
|
+
return { ok: true };
|
|
3701
|
+
const fallback = await execGit(cwd, ['rm', '--cached', '-r', '--', ...v.paths]);
|
|
3702
|
+
return fallback.code === 0 ? { ok: true } : { ok: false, error: gitErrorOutput(fallback) };
|
|
3703
|
+
}
|
|
3704
|
+
async function gitDiscard(cwd, paths) {
|
|
3705
|
+
const v = validatePaths(cwd, paths);
|
|
3706
|
+
if (!v.ok)
|
|
3707
|
+
return { ok: false, error: v.error };
|
|
3708
|
+
// For tracked files: `restore --worktree` reverts to HEAD. For untracked
|
|
3709
|
+
// files: that's a no-op and we delete them via `clean -f`. Run both so
|
|
3710
|
+
// the caller doesn't have to know which list each path is in.
|
|
3711
|
+
const restore = await execGit(cwd, ['restore', '--worktree', '--', ...v.paths]);
|
|
3712
|
+
const clean = await execGit(cwd, ['clean', '-f', '--', ...v.paths]);
|
|
3713
|
+
if (restore.code !== 0 && clean.code !== 0) {
|
|
3714
|
+
return { ok: false, error: gitErrorOutput(restore) || gitErrorOutput(clean) };
|
|
3715
|
+
}
|
|
3716
|
+
return { ok: true };
|
|
3717
|
+
}
|
|
3718
|
+
async function gitCommit(cwd, message, stageAll) {
|
|
3719
|
+
const trimmed = message.trim();
|
|
3720
|
+
if (!trimmed)
|
|
3721
|
+
return { ok: false, error: 'Commit message required' };
|
|
3722
|
+
if (stageAll) {
|
|
3723
|
+
const add = await execGit(cwd, ['add', '-A']);
|
|
3724
|
+
if (add.code !== 0)
|
|
3725
|
+
return { ok: false, error: gitErrorOutput(add) };
|
|
3726
|
+
}
|
|
3727
|
+
const res = await execGit(cwd, ['commit', '-m', trimmed]);
|
|
3728
|
+
if (res.code !== 0)
|
|
3729
|
+
return { ok: false, error: gitErrorOutput(res) };
|
|
3730
|
+
const hashRes = await execGit(cwd, ['rev-parse', 'HEAD']);
|
|
3731
|
+
return { ok: true, hash: hashRes.code === 0 ? hashRes.stdout.trim() : undefined };
|
|
3732
|
+
}
|
|
3733
|
+
async function gitPush(cwd) {
|
|
3734
|
+
const res = await execGit(cwd, ['push']);
|
|
3735
|
+
return res.code === 0
|
|
3736
|
+
? { ok: true, output: gitErrorOutput(res) }
|
|
3737
|
+
: { ok: false, error: gitErrorOutput(res) };
|
|
3738
|
+
}
|
|
3739
|
+
async function gitPull(cwd) {
|
|
3740
|
+
// `--ff-only` keeps the operation non-destructive: if the local branch has
|
|
3741
|
+
// diverged from upstream, we surface the error rather than auto-merging.
|
|
3742
|
+
// The user can resolve via the terminal or a future merge UI.
|
|
3743
|
+
const res = await execGit(cwd, ['pull', '--ff-only']);
|
|
3744
|
+
return res.code === 0
|
|
3745
|
+
? { ok: true, output: gitErrorOutput(res) }
|
|
3746
|
+
: { ok: false, error: gitErrorOutput(res) };
|
|
3747
|
+
}
|
|
3748
|
+
async function readGitBranches(cwd) {
|
|
3749
|
+
const isRepo = await execGit(cwd, ['rev-parse', '--is-inside-work-tree']);
|
|
3750
|
+
if (isRepo.code !== 0)
|
|
3751
|
+
return { inRepo: false, current: null, branches: [] };
|
|
3752
|
+
const cur = await execGit(cwd, ['rev-parse', '--abbrev-ref', 'HEAD']);
|
|
3753
|
+
const list = await execGit(cwd, ['branch', '--list', '--format=%(refname:short)']);
|
|
3754
|
+
const branches = list.code === 0
|
|
3755
|
+
? list.stdout.split('\n').map((s) => s.trim()).filter(Boolean)
|
|
3756
|
+
: [];
|
|
3757
|
+
return { inRepo: true, current: cur.code === 0 ? cur.stdout.trim() : null, branches };
|
|
3758
|
+
}
|
|
3759
|
+
async function readGitRemote(cwd) {
|
|
3760
|
+
const isRepo = await execGit(cwd, ['rev-parse', '--is-inside-work-tree']);
|
|
3761
|
+
if (isRepo.code !== 0)
|
|
3762
|
+
return { inRepo: false, url: null, name: null };
|
|
3763
|
+
const remoteName = await execGit(cwd, ['config', '--get', 'remote.pushDefault']);
|
|
3764
|
+
const name = remoteName.code === 0 && remoteName.stdout.trim() ? remoteName.stdout.trim() : 'origin';
|
|
3765
|
+
const url = await execGit(cwd, ['remote', 'get-url', name]);
|
|
3766
|
+
return { inRepo: true, url: url.code === 0 ? url.stdout.trim() : null, name };
|
|
3767
|
+
}
|
|
3768
|
+
async function gitCreateBranch(cwd, name, checkout) {
|
|
3769
|
+
const trimmed = name.trim();
|
|
3770
|
+
// Branch names can't start with `-` (would be parsed as a flag) and must be
|
|
3771
|
+
// non-empty. git itself enforces the rest of the ref-name rules.
|
|
3772
|
+
if (!trimmed)
|
|
3773
|
+
return { ok: false, error: 'Branch name required' };
|
|
3774
|
+
if (trimmed.startsWith('-'))
|
|
3775
|
+
return { ok: false, error: 'Invalid branch name' };
|
|
3776
|
+
const res = checkout
|
|
3777
|
+
? await execGit(cwd, ['checkout', '-b', trimmed])
|
|
3778
|
+
: await execGit(cwd, ['branch', trimmed]);
|
|
3779
|
+
return res.code === 0 ? { ok: true } : { ok: false, error: gitErrorOutput(res) };
|
|
3780
|
+
}
|
|
3781
|
+
async function gitCheckout(cwd, name) {
|
|
3782
|
+
const trimmed = name.trim();
|
|
3783
|
+
if (!trimmed)
|
|
3784
|
+
return { ok: false, error: 'Branch name required' };
|
|
3785
|
+
if (trimmed.startsWith('-'))
|
|
3786
|
+
return { ok: false, error: 'Invalid branch name' };
|
|
3787
|
+
const res = await execGit(cwd, ['checkout', trimmed]);
|
|
3788
|
+
return res.code === 0 ? { ok: true } : { ok: false, error: gitErrorOutput(res) };
|
|
3789
|
+
}
|
|
3544
3790
|
async function readHeadBlob(cwd, filePath) {
|
|
3545
3791
|
try {
|
|
3546
3792
|
const res = await execGit(cwd, ['show', `HEAD:${filePath}`]);
|