clivly 0.10.3 → 0.10.5
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/README.md +48 -1
- package/core-drizzle.d.cts +1 -1
- package/core-drizzle.d.mts +1 -1
- package/core-entity-config.cjs +4 -1
- package/core-entity-config.d.cts +2 -2
- package/core-entity-config.d.mts +2 -2
- package/core-entity-config.mjs +2 -2
- package/core-entity-heuristics.d.cts +1 -1
- package/core-entity-heuristics.d.mts +1 -1
- package/core-mapping-form.d.cts +1 -1
- package/core-mapping-form.d.mts +1 -1
- package/core-mappings-config.cjs +1 -1
- package/core-mappings-config.d.cts +1 -1
- package/core-mappings-config.d.mts +1 -1
- package/core-mappings-config.mjs +1 -1
- package/core-sync-engine.d.cts +1 -1
- package/core-sync-engine.d.mts +1 -1
- package/core-view-compiler.d.cts +1 -1
- package/core-view-compiler.d.mts +1 -1
- package/core.cjs +5 -2
- package/core.d.cts +8 -8
- package/core.d.mts +8 -8
- package/core.mjs +3 -3
- package/{drizzle-DTOuGXEn.d.cts → drizzle-SD8WCvYj.d.cts} +1 -1
- package/{drizzle-SGapoly2.d.mts → drizzle-axugRJ0u.d.mts} +1 -1
- package/drizzle.d.cts +3 -3
- package/drizzle.d.mts +3 -3
- package/{entity-config-DgAe0_YD.cjs → entity-config-C2MfIDL2.cjs} +49 -0
- package/{entity-config-C4d3K9CP.mjs → entity-config-bC3DX9f1.mjs} +32 -1
- package/{entity-config-DNQAMbMS.d.mts → entity-config-w8oMBWf8.d.cts} +28 -1
- package/{entity-config-DNQAMbMS.d.cts → entity-config-w8oMBWf8.d.mts} +28 -1
- package/index.cjs +192 -8
- package/index.mjs +192 -8
- package/{integration-state-D8SLu228.d.cts → integration-state-BA4c0RbD.d.cts} +15 -0
- package/{integration-state-D8SLu228.d.mts → integration-state-BA4c0RbD.d.mts} +15 -0
- package/{internal-BAOG5eE4.d.mts → internal-CHvZMvxC.d.cts} +1 -1
- package/{internal-BHJ0X3fC.d.cts → internal-WximMGmH.d.mts} +1 -1
- package/internal-drizzle.d.cts +1 -1
- package/internal-drizzle.d.mts +1 -1
- package/internal-entity-config.cjs +4 -1
- package/internal-entity-config.d.cts +2 -2
- package/internal-entity-config.d.mts +2 -2
- package/internal-entity-config.mjs +2 -2
- package/internal-entity-heuristics.cjs +1 -1
- package/internal-entity-heuristics.d.cts +1 -1
- package/internal-entity-heuristics.d.mts +1 -1
- package/internal-entity-heuristics.mjs +1 -1
- package/internal-mapping-form.d.cts +1 -1
- package/internal-mapping-form.d.mts +1 -1
- package/internal-mappings-config.cjs +1 -1
- package/internal-mappings-config.d.cts +1 -1
- package/internal-mappings-config.d.mts +1 -1
- package/internal-mappings-config.mjs +1 -1
- package/internal-sync-engine.d.cts +1 -1
- package/internal-sync-engine.d.mts +1 -1
- package/internal-view-compiler.d.cts +1 -1
- package/internal-view-compiler.d.mts +1 -1
- package/internal.cjs +5 -2
- package/internal.d.cts +8 -8
- package/internal.d.mts +8 -8
- package/internal.mjs +3 -3
- package/{mapping-form-Cc7Ewv8c.d.mts → mapping-form-BsTxv8w3.d.cts} +1 -1
- package/{mapping-form-DwdbYAfR.d.cts → mapping-form-OTFwr1sN.d.mts} +1 -1
- package/{mappings-config-MoXBeI8p.mjs → mappings-config-CRSL9dzw.mjs} +1 -1
- package/{mappings-config-Be9D43Np.d.mts → mappings-config-DbznSjTX.d.cts} +1 -1
- package/{mappings-config-Dx9ldgdf.cjs → mappings-config-Dpv08-x4.cjs} +1 -1
- package/{mappings-config-DUt4-9iO.d.cts → mappings-config-SXu_zlmp.d.mts} +1 -1
- package/{namespace-probe-D7GpEwLn.d.cts → namespace-probe-lj_0bJrc.d.cts} +2 -2
- package/{namespace-probe-DLve5TPy.d.mts → namespace-probe-ouHKZas2.d.mts} +2 -2
- package/package.json +1 -1
- package/{sdk-D6gX0TXS.mjs → sdk-BTzaM8bF.mjs} +2 -2
- package/{sdk-B1vX4kUJ.cjs → sdk-DbvqXNSh.cjs} +2 -2
- package/sdk.cjs +2 -2
- package/sdk.d.cts +2 -2
- package/sdk.d.mts +2 -2
- package/sdk.mjs +2 -2
- package/{sync-engine-Df0kHAoJ.d.cts → sync-engine-CUKWXUv7.d.mts} +2 -2
- package/{sync-engine-ugFacJ6G.d.mts → sync-engine-D2YlIHZB.d.cts} +2 -2
- package/{view-compiler-D3zp9S3K.d.cts → view-compiler-BO4CKamn.d.cts} +1 -1
- package/{view-compiler-TpzDTu7z.d.mts → view-compiler-iEwUOuba.d.mts} +1 -1
- package/vite.d.cts +1 -1
- package/vite.d.mts +1 -1
package/index.cjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const require_entity_config = require("./entity-config-
|
|
3
|
+
const require_entity_config = require("./entity-config-C2MfIDL2.cjs");
|
|
4
4
|
require("./mapping-score-CaB3F4Uu.cjs");
|
|
5
5
|
const require_internal_entity_heuristics = require("./internal-entity-heuristics.cjs");
|
|
6
|
-
const require_sdk = require("./sdk-
|
|
6
|
+
const require_sdk = require("./sdk-DbvqXNSh.cjs");
|
|
7
7
|
let node_child_process = require("node:child_process");
|
|
8
8
|
let node_fs = require("node:fs");
|
|
9
9
|
let node_module = require("node:module");
|
|
@@ -12,6 +12,7 @@ let node_path = require("node:path");
|
|
|
12
12
|
let node_readline_promises = require("node:readline/promises");
|
|
13
13
|
let jiti = require("jiti");
|
|
14
14
|
let node_url = require("node:url");
|
|
15
|
+
let node_net = require("node:net");
|
|
15
16
|
//#region ../core/src/env-file.ts
|
|
16
17
|
const ENV_FILES = {
|
|
17
18
|
next: ".env.local",
|
|
@@ -333,6 +334,28 @@ const TRIGGER_URL_PLACEHOLDER = `https://<yourDomain.com>${CLIVLY_TICK_PATH}`;
|
|
|
333
334
|
//#region src/dev-tunnel.ts
|
|
334
335
|
const CLOUDFLARE_QUICK_TUNNEL_REGEX = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/;
|
|
335
336
|
const DEFAULT_PORT = "3000";
|
|
337
|
+
/**
|
|
338
|
+
* The port each framework's dev server actually listens on out of the box.
|
|
339
|
+
*
|
|
340
|
+
* `clivly dev` used to tunnel to 3000 for everyone. Vite-based frameworks start
|
|
341
|
+
* on 5173, so the tunnel pointed at whatever else happened to hold 3000 — in one
|
|
342
|
+
* smoke run, a *different app entirely*, which answered, which made the tunnel
|
|
343
|
+
* look healthy while syncing the wrong project. Every symptom pointed away from
|
|
344
|
+
* the cause.
|
|
345
|
+
*
|
|
346
|
+
* These are defaults, not detections: a project that sets its own port must
|
|
347
|
+
* still pass `--port`. Guessing right for the untouched case is the win.
|
|
348
|
+
*/
|
|
349
|
+
const FRAMEWORK_DEFAULT_PORTS = {
|
|
350
|
+
remix: "5173",
|
|
351
|
+
sveltekit: "5173",
|
|
352
|
+
"tanstack-start": "5173",
|
|
353
|
+
nextjs: "3000",
|
|
354
|
+
nuxt: "3000"
|
|
355
|
+
};
|
|
356
|
+
function defaultPortFor(framework) {
|
|
357
|
+
return (framework && FRAMEWORK_DEFAULT_PORTS[framework]) ?? DEFAULT_PORT;
|
|
358
|
+
}
|
|
336
359
|
const DEFAULT_ROUTE_PATH = CLIVLY_TICK_PATH;
|
|
337
360
|
function parseTunnelUrl(chunk) {
|
|
338
361
|
const match = chunk.match(CLOUDFLARE_QUICK_TUNNEL_REGEX);
|
|
@@ -342,11 +365,50 @@ function buildTriggerUrl(origin, path) {
|
|
|
342
365
|
return `${origin.endsWith("/") ? origin.slice(0, -1) : origin}${path.startsWith("/") ? path : `/${path}`}`;
|
|
343
366
|
}
|
|
344
367
|
function resolveDevTarget(flags) {
|
|
368
|
+
const port = flags.port ?? defaultPortFor(flags.framework);
|
|
345
369
|
return {
|
|
346
|
-
target: flags.url ?? `http://localhost:${
|
|
347
|
-
triggerPath: flags.routePath ?? DEFAULT_ROUTE_PATH
|
|
370
|
+
target: flags.url ?? `http://localhost:${port}`,
|
|
371
|
+
triggerPath: flags.routePath ?? DEFAULT_ROUTE_PATH,
|
|
372
|
+
port
|
|
348
373
|
};
|
|
349
374
|
}
|
|
375
|
+
const VITE_FRAMEWORKS = /* @__PURE__ */ new Set([
|
|
376
|
+
"nuxt",
|
|
377
|
+
"remix",
|
|
378
|
+
"sveltekit",
|
|
379
|
+
"tanstack-start"
|
|
380
|
+
]);
|
|
381
|
+
function isViteFramework(framework) {
|
|
382
|
+
return framework ? VITE_FRAMEWORKS.has(framework) : false;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* The one config line that makes a Vite dev server answer the tunnel.
|
|
386
|
+
*
|
|
387
|
+
* Without it every sync fails with a bare "The host app returned 403" and the
|
|
388
|
+
* explanation — Vite's own, naming this exact setting — is buried in a
|
|
389
|
+
* diagnostics column. This affects four of the five supported frameworks, so
|
|
390
|
+
* `clivly dev` says it up front rather than letting each developer rediscover
|
|
391
|
+
* it. Printed unconditionally for Vite apps: reading the app's vite config well
|
|
392
|
+
* enough to know whether it is already set means parsing arbitrary TypeScript,
|
|
393
|
+
* and a redundant hint is far cheaper than a silent 403.
|
|
394
|
+
*/
|
|
395
|
+
function viteAllowedHostsHint() {
|
|
396
|
+
return [
|
|
397
|
+
"Vite blocks unknown hosts, so the tunnel needs one line in your Vite config:",
|
|
398
|
+
" server: { allowedHosts: [\".trycloudflare.com\"] }",
|
|
399
|
+
"Without it, sync fails with 403 before it reaches your app."
|
|
400
|
+
].join("\n");
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Warns that nothing is listening on the port about to be tunnelled.
|
|
404
|
+
*
|
|
405
|
+
* A quick tunnel to a dead port still resolves and still answers, so the failure
|
|
406
|
+
* surfaces later as an HTTP error attributed to the host app. Naming it here —
|
|
407
|
+
* while the developer is still looking at `clivly dev` output — costs one line.
|
|
408
|
+
*/
|
|
409
|
+
function deadPortMessage(port, framework) {
|
|
410
|
+
return [`Warning: nothing is listening on port ${port} yet, but the tunnel points there.${framework ? ` ${FRAMEWORK_LABELS[framework]} usually serves on ${defaultPortFor(framework)}.` : ""}`, "If your dev server uses a different port, restart with: clivly dev --port <port>"].join("\n");
|
|
411
|
+
}
|
|
350
412
|
function detectDevCommand(scripts, explicit) {
|
|
351
413
|
if (explicit && explicit.length > 0) return explicit;
|
|
352
414
|
if (scripts.dev) return [
|
|
@@ -389,6 +451,76 @@ function cloudflaredMissingMessage() {
|
|
|
389
451
|
].join("\n");
|
|
390
452
|
}
|
|
391
453
|
//#endregion
|
|
454
|
+
//#region src/tunnel-identity.ts
|
|
455
|
+
/**
|
|
456
|
+
* Whether the published tunnel actually reaches *this* app's Clivly route.
|
|
457
|
+
*
|
|
458
|
+
* `isLocalPortListening` answers a different question — "is anything accepting
|
|
459
|
+
* connections on this port?" — and that is exactly the gap this closes. On a
|
|
460
|
+
* machine running more than one project (which is what `clivly dev` is for),
|
|
461
|
+
* a framework that finds its default port taken silently moves to the next one:
|
|
462
|
+
*
|
|
463
|
+
* ⚠ Port 3000 is in use by process 92495, using available port 3001 instead
|
|
464
|
+
*
|
|
465
|
+
* The tunnel is still pointed at 3000, where a *different application* is
|
|
466
|
+
* listening. The port probe sees a healthy socket and says nothing, and the CLI
|
|
467
|
+
* goes on to promise "Dashboard-triggered sync now points at this local
|
|
468
|
+
* session" — which is false. Signed triggers for this workspace are delivered
|
|
469
|
+
* to somebody else's app, and the resulting failure is reported against the
|
|
470
|
+
* innocent one. Liveness is not identity.
|
|
471
|
+
*
|
|
472
|
+
* The check: POST to the trigger path with no signature. A Clivly-mounted route
|
|
473
|
+
* rejects that with **401** — it read the request, found no valid signature and
|
|
474
|
+
* refused. Anything else on that port answers some other way (a framework's
|
|
475
|
+
* 500 error page, a 404, an unrelated 200). We are not testing authentication
|
|
476
|
+
* here; the 401 *is* the fingerprint, and it is one no unrelated service
|
|
477
|
+
* produces at this exact path by accident.
|
|
478
|
+
*
|
|
479
|
+
* Deliberately tolerant: a network hiccup returns `unknown`, never `mismatch`.
|
|
480
|
+
* Warning someone away from a tunnel that was fine is worse than staying quiet,
|
|
481
|
+
* for the same reason the port probe defaults to assuming the port is alive.
|
|
482
|
+
*/
|
|
483
|
+
/** How long to wait before giving up and returning `unknown`. */
|
|
484
|
+
const IDENTITY_PROBE_TIMEOUT_MS = 8e3;
|
|
485
|
+
/**
|
|
486
|
+
* Probe the published tunnel URL and report who is on the other end.
|
|
487
|
+
*
|
|
488
|
+
* `fetchImpl` is injectable so tests do not need a network or a live tunnel.
|
|
489
|
+
*/
|
|
490
|
+
async function probeTunnelIdentity(triggerUrl, fetchImpl = fetch) {
|
|
491
|
+
const controller = new AbortController();
|
|
492
|
+
const timer = setTimeout(() => controller.abort(), IDENTITY_PROBE_TIMEOUT_MS);
|
|
493
|
+
try {
|
|
494
|
+
return (await fetchImpl(triggerUrl, {
|
|
495
|
+
method: "POST",
|
|
496
|
+
headers: { "content-type": "application/json" },
|
|
497
|
+
body: "{}",
|
|
498
|
+
signal: controller.signal
|
|
499
|
+
})).status === 401 ? "clivly" : "mismatch";
|
|
500
|
+
} catch {
|
|
501
|
+
return "unknown";
|
|
502
|
+
} finally {
|
|
503
|
+
clearTimeout(timer);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* What to tell someone whose tunnel reaches the wrong process.
|
|
508
|
+
*
|
|
509
|
+
* Names the likely cause rather than only the symptom: the overwhelmingly
|
|
510
|
+
* common reason is the port fallback above, and the remedy is to say which
|
|
511
|
+
* port the app really bound.
|
|
512
|
+
*/
|
|
513
|
+
function tunnelMismatchMessage(port) {
|
|
514
|
+
return [
|
|
515
|
+
`Warning: the tunnel reaches something on port ${port}, but it is not this app's Clivly route.`,
|
|
516
|
+
` The usual cause is a port fallback. If your dev server printed "Port ${port} is in use,`,
|
|
517
|
+
` using available port …", it moved and the tunnel stayed on ${port} — where another`,
|
|
518
|
+
" project is still listening.",
|
|
519
|
+
" Re-run with the port it actually bound: npx clivly dev --port <port>",
|
|
520
|
+
" Until then, dashboard-triggered syncs will not reach this app."
|
|
521
|
+
].join("\n");
|
|
522
|
+
}
|
|
523
|
+
//#endregion
|
|
392
524
|
//#region src/dev.ts
|
|
393
525
|
const UNREACHABLE_RESULT = {
|
|
394
526
|
ok: false,
|
|
@@ -404,10 +536,12 @@ function explicitCommand(argv) {
|
|
|
404
536
|
}
|
|
405
537
|
async function runDev(options) {
|
|
406
538
|
const { deps } = options;
|
|
407
|
-
const
|
|
539
|
+
const framework = deps.detectFramework?.(options.cwd);
|
|
540
|
+
const { target, triggerPath, port } = resolveDevTarget({
|
|
408
541
|
url: flagValue$1(options.argv, "--url"),
|
|
409
542
|
port: flagValue$1(options.argv, "--port"),
|
|
410
|
-
routePath: flagValue$1(options.argv, "--route-path")
|
|
543
|
+
routePath: flagValue$1(options.argv, "--route-path"),
|
|
544
|
+
framework
|
|
411
545
|
});
|
|
412
546
|
const command = detectDevCommand(deps.readScripts(options.cwd), explicitCommand(options.argv));
|
|
413
547
|
if (!deps.hasCloudflared()) {
|
|
@@ -424,6 +558,7 @@ async function runDev(options) {
|
|
|
424
558
|
deps.out("CLIVLY_SECRET_KEY is not set — cannot register the tunnel URL. Set it and re-run.");
|
|
425
559
|
return 1;
|
|
426
560
|
}
|
|
561
|
+
if (isViteFramework(framework)) deps.out(viteAllowedHostsHint());
|
|
427
562
|
const app = command ? deps.spawnApp(command) : null;
|
|
428
563
|
const tunnel = deps.spawnTunnel(target);
|
|
429
564
|
let shuttingDown = false;
|
|
@@ -431,8 +566,13 @@ async function runDev(options) {
|
|
|
431
566
|
if (shuttingDown) return;
|
|
432
567
|
const triggerUrl = buildTriggerUrl(origin, triggerPath);
|
|
433
568
|
deps.out(`Tunnel: ${triggerUrl}`);
|
|
569
|
+
if (deps.isPortListening && !flagValue$1(options.argv, "--url")) {
|
|
570
|
+
if (!await deps.isPortListening(port).catch(() => true)) deps.out(deadPortMessage(port, framework));
|
|
571
|
+
}
|
|
572
|
+
const identity = deps.probeTunnel && !flagValue$1(options.argv, "--url") ? await deps.probeTunnel(triggerUrl).catch(() => "unknown") : "unknown";
|
|
573
|
+
if (identity === "mismatch") deps.out(tunnelMismatchMessage(port));
|
|
434
574
|
const result = await deps.register(triggerUrl).catch(() => UNREACHABLE_RESULT);
|
|
435
|
-
if (result.ok) deps.out("Dashboard-triggered sync now points at this local session.");
|
|
575
|
+
if (result.ok) deps.out(identity === "mismatch" ? "Trigger URL registered, but see the warning above before relying on it." : "Dashboard-triggered sync now points at this local session.");
|
|
436
576
|
else deps.out(overrideFailureMessage(result.kind, result.status));
|
|
437
577
|
});
|
|
438
578
|
const stopWatching = deps.watchConfig?.(options.cwd, () => {
|
|
@@ -1809,6 +1949,10 @@ const NEXT_INSTRUMENTATION_FLAG_STEP = `Your Next.js version reads instrumentati
|
|
|
1809
1949
|
const REMIX_BOOT_STEP = `Start Clivly when your app boots. Add this near the top of app/entry.server.tsx:
|
|
1810
1950
|
import clivly from "./clivly.config";
|
|
1811
1951
|
clivly.start().catch((error) => console.warn("[clivly] could not check in:", error));`;
|
|
1952
|
+
const MISSING_VITE_CONFIG_STEP = `Could not find a vite config, so nothing starts Clivly when your app boots. Add the plugin to your Vite config:
|
|
1953
|
+
import { clivlyVite } from "clivly/vite";
|
|
1954
|
+
import clivly from "./clivly.config";
|
|
1955
|
+
// then, inside defineConfig: plugins: [clivlyVite(clivly), …]`;
|
|
1812
1956
|
/** The boot hook as a scaffold entry, or null when this framework is patched. */
|
|
1813
1957
|
function planBootHook(framework, layout) {
|
|
1814
1958
|
const path = bootHookPath(framework, layout);
|
|
@@ -2402,7 +2546,7 @@ function planBootPatch(args) {
|
|
|
2402
2546
|
}
|
|
2403
2547
|
return {
|
|
2404
2548
|
file: null,
|
|
2405
|
-
manual:
|
|
2549
|
+
manual: MISSING_VITE_CONFIG_STEP
|
|
2406
2550
|
};
|
|
2407
2551
|
}
|
|
2408
2552
|
function planMutations(args) {
|
|
@@ -2426,6 +2570,43 @@ function planWidgetMutations(args) {
|
|
|
2426
2570
|
});
|
|
2427
2571
|
}
|
|
2428
2572
|
//#endregion
|
|
2573
|
+
//#region src/port-probe.ts
|
|
2574
|
+
const PORT_PROBE_TIMEOUT_MS = 1500;
|
|
2575
|
+
/** One connect attempt against a single address family. */
|
|
2576
|
+
function connectsOn(port, host) {
|
|
2577
|
+
return new Promise((resolve) => {
|
|
2578
|
+
const socket = new node_net.Socket();
|
|
2579
|
+
let settled = false;
|
|
2580
|
+
const finish = (listening) => {
|
|
2581
|
+
if (settled) return;
|
|
2582
|
+
settled = true;
|
|
2583
|
+
socket.destroy();
|
|
2584
|
+
resolve(listening);
|
|
2585
|
+
};
|
|
2586
|
+
socket.setTimeout(PORT_PROBE_TIMEOUT_MS);
|
|
2587
|
+
socket.once("connect", () => finish(true));
|
|
2588
|
+
socket.once("timeout", () => finish(false));
|
|
2589
|
+
socket.once("error", () => finish(false));
|
|
2590
|
+
socket.connect(Number(port), host);
|
|
2591
|
+
});
|
|
2592
|
+
}
|
|
2593
|
+
/**
|
|
2594
|
+
* Whether anything is accepting TCP connections on a local port.
|
|
2595
|
+
*
|
|
2596
|
+
* Deliberately a bare socket connect rather than an HTTP request: the dev
|
|
2597
|
+
* server may legitimately answer 404/403/500 on `/`, and any of those still
|
|
2598
|
+
* proves something is listening. Only a refused or timed-out connection means
|
|
2599
|
+
* the tunnel is pointed at nothing.
|
|
2600
|
+
*
|
|
2601
|
+
* Both address families are tried, because Vite binds `::1` ONLY by default —
|
|
2602
|
+
* so an IPv4-only probe reports "nothing is listening" while the dev server is
|
|
2603
|
+
* running perfectly. A false warning here is worse than none: it sends the
|
|
2604
|
+
* developer to fix a port that was already correct.
|
|
2605
|
+
*/
|
|
2606
|
+
function isLocalPortListening(port) {
|
|
2607
|
+
return Promise.all([connectsOn(port, "127.0.0.1"), connectsOn(port, "::1")]).then((results) => results.some(Boolean));
|
|
2608
|
+
}
|
|
2609
|
+
//#endregion
|
|
2429
2610
|
//#region src/quiet-host-output.ts
|
|
2430
2611
|
/**
|
|
2431
2612
|
* Run `fn` with the process's stdout/stderr captured. Always restores the
|
|
@@ -4005,6 +4186,9 @@ function defaultDevDeps(options) {
|
|
|
4005
4186
|
spawnTunnel: spawnCloudflaredTunnel,
|
|
4006
4187
|
spawnApp: spawnAppProcess,
|
|
4007
4188
|
readScripts: readPackageScripts,
|
|
4189
|
+
detectFramework: (cwd) => detectFramework(readDeps(cwd)),
|
|
4190
|
+
isPortListening: isLocalPortListening,
|
|
4191
|
+
probeTunnel: (triggerUrl) => probeTunnelIdentity(triggerUrl),
|
|
4008
4192
|
register: (triggerUrl) => registerTunnelOverride({
|
|
4009
4193
|
apiKey: options.apiKey ?? "",
|
|
4010
4194
|
apiUrl: options.apiUrl,
|
package/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as CANONICAL_FIELDS } from "./entity-config-
|
|
2
|
+
import { t as CANONICAL_FIELDS } from "./entity-config-bC3DX9f1.mjs";
|
|
3
3
|
import "./mapping-score-C68uAqGB.mjs";
|
|
4
4
|
import { hasDefiningColumn, matchesConcept, requiredFieldsFor, scoreFieldMap, scoreRelationships, suggestCursorField, toFieldMap } from "./internal-entity-heuristics.mjs";
|
|
5
|
-
import { s as isPlaceholderUrl, t as createClivlySDK } from "./sdk-
|
|
5
|
+
import { s as isPlaceholderUrl, t as createClivlySDK } from "./sdk-BTzaM8bF.mjs";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
7
|
import { spawn } from "node:child_process";
|
|
8
8
|
import { accessSync, constants, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync, watch, writeFileSync } from "node:fs";
|
|
@@ -11,6 +11,7 @@ import { delimiter, dirname, isAbsolute, join, relative, resolve, sep } from "no
|
|
|
11
11
|
import { createInterface } from "node:readline/promises";
|
|
12
12
|
import { createJiti } from "jiti";
|
|
13
13
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
14
|
+
import { Socket } from "node:net";
|
|
14
15
|
//#region ../core/src/env-file.ts
|
|
15
16
|
const ENV_FILES = {
|
|
16
17
|
next: ".env.local",
|
|
@@ -332,6 +333,28 @@ const TRIGGER_URL_PLACEHOLDER = `https://<yourDomain.com>${CLIVLY_TICK_PATH}`;
|
|
|
332
333
|
//#region src/dev-tunnel.ts
|
|
333
334
|
const CLOUDFLARE_QUICK_TUNNEL_REGEX = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/;
|
|
334
335
|
const DEFAULT_PORT = "3000";
|
|
336
|
+
/**
|
|
337
|
+
* The port each framework's dev server actually listens on out of the box.
|
|
338
|
+
*
|
|
339
|
+
* `clivly dev` used to tunnel to 3000 for everyone. Vite-based frameworks start
|
|
340
|
+
* on 5173, so the tunnel pointed at whatever else happened to hold 3000 — in one
|
|
341
|
+
* smoke run, a *different app entirely*, which answered, which made the tunnel
|
|
342
|
+
* look healthy while syncing the wrong project. Every symptom pointed away from
|
|
343
|
+
* the cause.
|
|
344
|
+
*
|
|
345
|
+
* These are defaults, not detections: a project that sets its own port must
|
|
346
|
+
* still pass `--port`. Guessing right for the untouched case is the win.
|
|
347
|
+
*/
|
|
348
|
+
const FRAMEWORK_DEFAULT_PORTS = {
|
|
349
|
+
remix: "5173",
|
|
350
|
+
sveltekit: "5173",
|
|
351
|
+
"tanstack-start": "5173",
|
|
352
|
+
nextjs: "3000",
|
|
353
|
+
nuxt: "3000"
|
|
354
|
+
};
|
|
355
|
+
function defaultPortFor(framework) {
|
|
356
|
+
return (framework && FRAMEWORK_DEFAULT_PORTS[framework]) ?? DEFAULT_PORT;
|
|
357
|
+
}
|
|
335
358
|
const DEFAULT_ROUTE_PATH = CLIVLY_TICK_PATH;
|
|
336
359
|
function parseTunnelUrl(chunk) {
|
|
337
360
|
const match = chunk.match(CLOUDFLARE_QUICK_TUNNEL_REGEX);
|
|
@@ -341,11 +364,50 @@ function buildTriggerUrl(origin, path) {
|
|
|
341
364
|
return `${origin.endsWith("/") ? origin.slice(0, -1) : origin}${path.startsWith("/") ? path : `/${path}`}`;
|
|
342
365
|
}
|
|
343
366
|
function resolveDevTarget(flags) {
|
|
367
|
+
const port = flags.port ?? defaultPortFor(flags.framework);
|
|
344
368
|
return {
|
|
345
|
-
target: flags.url ?? `http://localhost:${
|
|
346
|
-
triggerPath: flags.routePath ?? DEFAULT_ROUTE_PATH
|
|
369
|
+
target: flags.url ?? `http://localhost:${port}`,
|
|
370
|
+
triggerPath: flags.routePath ?? DEFAULT_ROUTE_PATH,
|
|
371
|
+
port
|
|
347
372
|
};
|
|
348
373
|
}
|
|
374
|
+
const VITE_FRAMEWORKS = /* @__PURE__ */ new Set([
|
|
375
|
+
"nuxt",
|
|
376
|
+
"remix",
|
|
377
|
+
"sveltekit",
|
|
378
|
+
"tanstack-start"
|
|
379
|
+
]);
|
|
380
|
+
function isViteFramework(framework) {
|
|
381
|
+
return framework ? VITE_FRAMEWORKS.has(framework) : false;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* The one config line that makes a Vite dev server answer the tunnel.
|
|
385
|
+
*
|
|
386
|
+
* Without it every sync fails with a bare "The host app returned 403" and the
|
|
387
|
+
* explanation — Vite's own, naming this exact setting — is buried in a
|
|
388
|
+
* diagnostics column. This affects four of the five supported frameworks, so
|
|
389
|
+
* `clivly dev` says it up front rather than letting each developer rediscover
|
|
390
|
+
* it. Printed unconditionally for Vite apps: reading the app's vite config well
|
|
391
|
+
* enough to know whether it is already set means parsing arbitrary TypeScript,
|
|
392
|
+
* and a redundant hint is far cheaper than a silent 403.
|
|
393
|
+
*/
|
|
394
|
+
function viteAllowedHostsHint() {
|
|
395
|
+
return [
|
|
396
|
+
"Vite blocks unknown hosts, so the tunnel needs one line in your Vite config:",
|
|
397
|
+
" server: { allowedHosts: [\".trycloudflare.com\"] }",
|
|
398
|
+
"Without it, sync fails with 403 before it reaches your app."
|
|
399
|
+
].join("\n");
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Warns that nothing is listening on the port about to be tunnelled.
|
|
403
|
+
*
|
|
404
|
+
* A quick tunnel to a dead port still resolves and still answers, so the failure
|
|
405
|
+
* surfaces later as an HTTP error attributed to the host app. Naming it here —
|
|
406
|
+
* while the developer is still looking at `clivly dev` output — costs one line.
|
|
407
|
+
*/
|
|
408
|
+
function deadPortMessage(port, framework) {
|
|
409
|
+
return [`Warning: nothing is listening on port ${port} yet, but the tunnel points there.${framework ? ` ${FRAMEWORK_LABELS[framework]} usually serves on ${defaultPortFor(framework)}.` : ""}`, "If your dev server uses a different port, restart with: clivly dev --port <port>"].join("\n");
|
|
410
|
+
}
|
|
349
411
|
function detectDevCommand(scripts, explicit) {
|
|
350
412
|
if (explicit && explicit.length > 0) return explicit;
|
|
351
413
|
if (scripts.dev) return [
|
|
@@ -388,6 +450,76 @@ function cloudflaredMissingMessage() {
|
|
|
388
450
|
].join("\n");
|
|
389
451
|
}
|
|
390
452
|
//#endregion
|
|
453
|
+
//#region src/tunnel-identity.ts
|
|
454
|
+
/**
|
|
455
|
+
* Whether the published tunnel actually reaches *this* app's Clivly route.
|
|
456
|
+
*
|
|
457
|
+
* `isLocalPortListening` answers a different question — "is anything accepting
|
|
458
|
+
* connections on this port?" — and that is exactly the gap this closes. On a
|
|
459
|
+
* machine running more than one project (which is what `clivly dev` is for),
|
|
460
|
+
* a framework that finds its default port taken silently moves to the next one:
|
|
461
|
+
*
|
|
462
|
+
* ⚠ Port 3000 is in use by process 92495, using available port 3001 instead
|
|
463
|
+
*
|
|
464
|
+
* The tunnel is still pointed at 3000, where a *different application* is
|
|
465
|
+
* listening. The port probe sees a healthy socket and says nothing, and the CLI
|
|
466
|
+
* goes on to promise "Dashboard-triggered sync now points at this local
|
|
467
|
+
* session" — which is false. Signed triggers for this workspace are delivered
|
|
468
|
+
* to somebody else's app, and the resulting failure is reported against the
|
|
469
|
+
* innocent one. Liveness is not identity.
|
|
470
|
+
*
|
|
471
|
+
* The check: POST to the trigger path with no signature. A Clivly-mounted route
|
|
472
|
+
* rejects that with **401** — it read the request, found no valid signature and
|
|
473
|
+
* refused. Anything else on that port answers some other way (a framework's
|
|
474
|
+
* 500 error page, a 404, an unrelated 200). We are not testing authentication
|
|
475
|
+
* here; the 401 *is* the fingerprint, and it is one no unrelated service
|
|
476
|
+
* produces at this exact path by accident.
|
|
477
|
+
*
|
|
478
|
+
* Deliberately tolerant: a network hiccup returns `unknown`, never `mismatch`.
|
|
479
|
+
* Warning someone away from a tunnel that was fine is worse than staying quiet,
|
|
480
|
+
* for the same reason the port probe defaults to assuming the port is alive.
|
|
481
|
+
*/
|
|
482
|
+
/** How long to wait before giving up and returning `unknown`. */
|
|
483
|
+
const IDENTITY_PROBE_TIMEOUT_MS = 8e3;
|
|
484
|
+
/**
|
|
485
|
+
* Probe the published tunnel URL and report who is on the other end.
|
|
486
|
+
*
|
|
487
|
+
* `fetchImpl` is injectable so tests do not need a network or a live tunnel.
|
|
488
|
+
*/
|
|
489
|
+
async function probeTunnelIdentity(triggerUrl, fetchImpl = fetch) {
|
|
490
|
+
const controller = new AbortController();
|
|
491
|
+
const timer = setTimeout(() => controller.abort(), IDENTITY_PROBE_TIMEOUT_MS);
|
|
492
|
+
try {
|
|
493
|
+
return (await fetchImpl(triggerUrl, {
|
|
494
|
+
method: "POST",
|
|
495
|
+
headers: { "content-type": "application/json" },
|
|
496
|
+
body: "{}",
|
|
497
|
+
signal: controller.signal
|
|
498
|
+
})).status === 401 ? "clivly" : "mismatch";
|
|
499
|
+
} catch {
|
|
500
|
+
return "unknown";
|
|
501
|
+
} finally {
|
|
502
|
+
clearTimeout(timer);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* What to tell someone whose tunnel reaches the wrong process.
|
|
507
|
+
*
|
|
508
|
+
* Names the likely cause rather than only the symptom: the overwhelmingly
|
|
509
|
+
* common reason is the port fallback above, and the remedy is to say which
|
|
510
|
+
* port the app really bound.
|
|
511
|
+
*/
|
|
512
|
+
function tunnelMismatchMessage(port) {
|
|
513
|
+
return [
|
|
514
|
+
`Warning: the tunnel reaches something on port ${port}, but it is not this app's Clivly route.`,
|
|
515
|
+
` The usual cause is a port fallback. If your dev server printed "Port ${port} is in use,`,
|
|
516
|
+
` using available port …", it moved and the tunnel stayed on ${port} — where another`,
|
|
517
|
+
" project is still listening.",
|
|
518
|
+
" Re-run with the port it actually bound: npx clivly dev --port <port>",
|
|
519
|
+
" Until then, dashboard-triggered syncs will not reach this app."
|
|
520
|
+
].join("\n");
|
|
521
|
+
}
|
|
522
|
+
//#endregion
|
|
391
523
|
//#region src/dev.ts
|
|
392
524
|
const UNREACHABLE_RESULT = {
|
|
393
525
|
ok: false,
|
|
@@ -403,10 +535,12 @@ function explicitCommand(argv) {
|
|
|
403
535
|
}
|
|
404
536
|
async function runDev(options) {
|
|
405
537
|
const { deps } = options;
|
|
406
|
-
const
|
|
538
|
+
const framework = deps.detectFramework?.(options.cwd);
|
|
539
|
+
const { target, triggerPath, port } = resolveDevTarget({
|
|
407
540
|
url: flagValue$1(options.argv, "--url"),
|
|
408
541
|
port: flagValue$1(options.argv, "--port"),
|
|
409
|
-
routePath: flagValue$1(options.argv, "--route-path")
|
|
542
|
+
routePath: flagValue$1(options.argv, "--route-path"),
|
|
543
|
+
framework
|
|
410
544
|
});
|
|
411
545
|
const command = detectDevCommand(deps.readScripts(options.cwd), explicitCommand(options.argv));
|
|
412
546
|
if (!deps.hasCloudflared()) {
|
|
@@ -423,6 +557,7 @@ async function runDev(options) {
|
|
|
423
557
|
deps.out("CLIVLY_SECRET_KEY is not set — cannot register the tunnel URL. Set it and re-run.");
|
|
424
558
|
return 1;
|
|
425
559
|
}
|
|
560
|
+
if (isViteFramework(framework)) deps.out(viteAllowedHostsHint());
|
|
426
561
|
const app = command ? deps.spawnApp(command) : null;
|
|
427
562
|
const tunnel = deps.spawnTunnel(target);
|
|
428
563
|
let shuttingDown = false;
|
|
@@ -430,8 +565,13 @@ async function runDev(options) {
|
|
|
430
565
|
if (shuttingDown) return;
|
|
431
566
|
const triggerUrl = buildTriggerUrl(origin, triggerPath);
|
|
432
567
|
deps.out(`Tunnel: ${triggerUrl}`);
|
|
568
|
+
if (deps.isPortListening && !flagValue$1(options.argv, "--url")) {
|
|
569
|
+
if (!await deps.isPortListening(port).catch(() => true)) deps.out(deadPortMessage(port, framework));
|
|
570
|
+
}
|
|
571
|
+
const identity = deps.probeTunnel && !flagValue$1(options.argv, "--url") ? await deps.probeTunnel(triggerUrl).catch(() => "unknown") : "unknown";
|
|
572
|
+
if (identity === "mismatch") deps.out(tunnelMismatchMessage(port));
|
|
433
573
|
const result = await deps.register(triggerUrl).catch(() => UNREACHABLE_RESULT);
|
|
434
|
-
if (result.ok) deps.out("Dashboard-triggered sync now points at this local session.");
|
|
574
|
+
if (result.ok) deps.out(identity === "mismatch" ? "Trigger URL registered, but see the warning above before relying on it." : "Dashboard-triggered sync now points at this local session.");
|
|
435
575
|
else deps.out(overrideFailureMessage(result.kind, result.status));
|
|
436
576
|
});
|
|
437
577
|
const stopWatching = deps.watchConfig?.(options.cwd, () => {
|
|
@@ -1808,6 +1948,10 @@ const NEXT_INSTRUMENTATION_FLAG_STEP = `Your Next.js version reads instrumentati
|
|
|
1808
1948
|
const REMIX_BOOT_STEP = `Start Clivly when your app boots. Add this near the top of app/entry.server.tsx:
|
|
1809
1949
|
import clivly from "./clivly.config";
|
|
1810
1950
|
clivly.start().catch((error) => console.warn("[clivly] could not check in:", error));`;
|
|
1951
|
+
const MISSING_VITE_CONFIG_STEP = `Could not find a vite config, so nothing starts Clivly when your app boots. Add the plugin to your Vite config:
|
|
1952
|
+
import { clivlyVite } from "clivly/vite";
|
|
1953
|
+
import clivly from "./clivly.config";
|
|
1954
|
+
// then, inside defineConfig: plugins: [clivlyVite(clivly), …]`;
|
|
1811
1955
|
/** The boot hook as a scaffold entry, or null when this framework is patched. */
|
|
1812
1956
|
function planBootHook(framework, layout) {
|
|
1813
1957
|
const path = bootHookPath(framework, layout);
|
|
@@ -2401,7 +2545,7 @@ function planBootPatch(args) {
|
|
|
2401
2545
|
}
|
|
2402
2546
|
return {
|
|
2403
2547
|
file: null,
|
|
2404
|
-
manual:
|
|
2548
|
+
manual: MISSING_VITE_CONFIG_STEP
|
|
2405
2549
|
};
|
|
2406
2550
|
}
|
|
2407
2551
|
function planMutations(args) {
|
|
@@ -2425,6 +2569,43 @@ function planWidgetMutations(args) {
|
|
|
2425
2569
|
});
|
|
2426
2570
|
}
|
|
2427
2571
|
//#endregion
|
|
2572
|
+
//#region src/port-probe.ts
|
|
2573
|
+
const PORT_PROBE_TIMEOUT_MS = 1500;
|
|
2574
|
+
/** One connect attempt against a single address family. */
|
|
2575
|
+
function connectsOn(port, host) {
|
|
2576
|
+
return new Promise((resolve) => {
|
|
2577
|
+
const socket = new Socket();
|
|
2578
|
+
let settled = false;
|
|
2579
|
+
const finish = (listening) => {
|
|
2580
|
+
if (settled) return;
|
|
2581
|
+
settled = true;
|
|
2582
|
+
socket.destroy();
|
|
2583
|
+
resolve(listening);
|
|
2584
|
+
};
|
|
2585
|
+
socket.setTimeout(PORT_PROBE_TIMEOUT_MS);
|
|
2586
|
+
socket.once("connect", () => finish(true));
|
|
2587
|
+
socket.once("timeout", () => finish(false));
|
|
2588
|
+
socket.once("error", () => finish(false));
|
|
2589
|
+
socket.connect(Number(port), host);
|
|
2590
|
+
});
|
|
2591
|
+
}
|
|
2592
|
+
/**
|
|
2593
|
+
* Whether anything is accepting TCP connections on a local port.
|
|
2594
|
+
*
|
|
2595
|
+
* Deliberately a bare socket connect rather than an HTTP request: the dev
|
|
2596
|
+
* server may legitimately answer 404/403/500 on `/`, and any of those still
|
|
2597
|
+
* proves something is listening. Only a refused or timed-out connection means
|
|
2598
|
+
* the tunnel is pointed at nothing.
|
|
2599
|
+
*
|
|
2600
|
+
* Both address families are tried, because Vite binds `::1` ONLY by default —
|
|
2601
|
+
* so an IPv4-only probe reports "nothing is listening" while the dev server is
|
|
2602
|
+
* running perfectly. A false warning here is worse than none: it sends the
|
|
2603
|
+
* developer to fix a port that was already correct.
|
|
2604
|
+
*/
|
|
2605
|
+
function isLocalPortListening(port) {
|
|
2606
|
+
return Promise.all([connectsOn(port, "127.0.0.1"), connectsOn(port, "::1")]).then((results) => results.some(Boolean));
|
|
2607
|
+
}
|
|
2608
|
+
//#endregion
|
|
2428
2609
|
//#region src/quiet-host-output.ts
|
|
2429
2610
|
/**
|
|
2430
2611
|
* Run `fn` with the process's stdout/stderr captured. Always restores the
|
|
@@ -4004,6 +4185,9 @@ function defaultDevDeps(options) {
|
|
|
4004
4185
|
spawnTunnel: spawnCloudflaredTunnel,
|
|
4005
4186
|
spawnApp: spawnAppProcess,
|
|
4006
4187
|
readScripts: readPackageScripts,
|
|
4188
|
+
detectFramework: (cwd) => detectFramework(readDeps(cwd)),
|
|
4189
|
+
isPortListening: isLocalPortListening,
|
|
4190
|
+
probeTunnel: (triggerUrl) => probeTunnelIdentity(triggerUrl),
|
|
4007
4191
|
register: (triggerUrl) => registerTunnelOverride({
|
|
4008
4192
|
apiKey: options.apiKey ?? "",
|
|
4009
4193
|
apiUrl: options.apiUrl,
|
|
@@ -14,6 +14,14 @@ interface RunFact {
|
|
|
14
14
|
at: number;
|
|
15
15
|
message?: string;
|
|
16
16
|
ok: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* The HTTP status the host answered with, when there was one.
|
|
19
|
+
*
|
|
20
|
+
* Absent for a run that failed before any response existed, and for callers
|
|
21
|
+
* that do not track it — so every read must treat `undefined` as "unknown",
|
|
22
|
+
* never as "fine".
|
|
23
|
+
*/
|
|
24
|
+
status?: number;
|
|
17
25
|
}
|
|
18
26
|
/**
|
|
19
27
|
* Where the reporting app is running, as the app itself understands it.
|
|
@@ -44,6 +52,13 @@ interface IntegrationFacts {
|
|
|
44
52
|
heartbeatIntervalMs?: number | null;
|
|
45
53
|
lastHeartbeatAt: number | null;
|
|
46
54
|
lastRun: RunFact | null;
|
|
55
|
+
/**
|
|
56
|
+
* Whether any entity is mapped, expressed as a number for historical
|
|
57
|
+
* reasons — every producer sets it to 0 or 1, never a real total. Read it as
|
|
58
|
+
* a flag (`> 0`) and never render it as a quantity: doing so reported "1
|
|
59
|
+
* entity mapped" to a workspace with three. The true count lives on the
|
|
60
|
+
* mappings list, which the What syncs tab reads directly.
|
|
61
|
+
*/
|
|
47
62
|
mappedEntityCount: number;
|
|
48
63
|
now: number;
|
|
49
64
|
/**
|
|
@@ -14,6 +14,14 @@ interface RunFact {
|
|
|
14
14
|
at: number;
|
|
15
15
|
message?: string;
|
|
16
16
|
ok: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* The HTTP status the host answered with, when there was one.
|
|
19
|
+
*
|
|
20
|
+
* Absent for a run that failed before any response existed, and for callers
|
|
21
|
+
* that do not track it — so every read must treat `undefined` as "unknown",
|
|
22
|
+
* never as "fine".
|
|
23
|
+
*/
|
|
24
|
+
status?: number;
|
|
17
25
|
}
|
|
18
26
|
/**
|
|
19
27
|
* Where the reporting app is running, as the app itself understands it.
|
|
@@ -44,6 +52,13 @@ interface IntegrationFacts {
|
|
|
44
52
|
heartbeatIntervalMs?: number | null;
|
|
45
53
|
lastHeartbeatAt: number | null;
|
|
46
54
|
lastRun: RunFact | null;
|
|
55
|
+
/**
|
|
56
|
+
* Whether any entity is mapped, expressed as a number for historical
|
|
57
|
+
* reasons — every producer sets it to 0 or 1, never a real total. Read it as
|
|
58
|
+
* a flag (`> 0`) and never render it as a quantity: doing so reported "1
|
|
59
|
+
* entity mapped" to a workspace with three. The true count lives on the
|
|
60
|
+
* mappings list, which the What syncs tab reads directly.
|
|
61
|
+
*/
|
|
47
62
|
mappedEntityCount: number;
|
|
48
63
|
now: number;
|
|
49
64
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { L as ProjectionJoin, R as ProjectionMapping, h as FilterExpr, l as ConfigValidationError, m as DiscoveredSchemaTable, p as DiscoveredColumnMeta } from "./entity-config-w8oMBWf8.cjs";
|
|
2
2
|
//#region ../core/src/projection-cardinality.d.ts
|
|
3
3
|
declare function joinFansOut(join: ProjectionJoin, joinTable: DiscoveredSchemaTable): boolean;
|
|
4
4
|
/** Every join in the projection that can multiply rows. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { L as ProjectionJoin, R as ProjectionMapping, h as FilterExpr, l as ConfigValidationError, m as DiscoveredSchemaTable, p as DiscoveredColumnMeta } from "./entity-config-w8oMBWf8.mjs";
|
|
2
2
|
//#region ../core/src/projection-cardinality.d.ts
|
|
3
3
|
declare function joinFansOut(join: ProjectionJoin, joinTable: DiscoveredSchemaTable): boolean;
|
|
4
4
|
/** Every join in the projection that can multiply rows. */
|
package/internal-drizzle.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as buildUniqueConstraints, r as discoverFromDrizzle, t as buildColumnsMeta } from "./drizzle-
|
|
1
|
+
import { n as buildUniqueConstraints, r as discoverFromDrizzle, t as buildColumnsMeta } from "./drizzle-SD8WCvYj.cjs";
|
|
2
2
|
export { buildColumnsMeta, buildUniqueConstraints, discoverFromDrizzle };
|
package/internal-drizzle.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as buildUniqueConstraints, r as discoverFromDrizzle, t as buildColumnsMeta } from "./drizzle-
|
|
1
|
+
import { n as buildUniqueConstraints, r as discoverFromDrizzle, t as buildColumnsMeta } from "./drizzle-axugRJ0u.mjs";
|
|
2
2
|
export { buildColumnsMeta, buildUniqueConstraints, discoverFromDrizzle };
|