clivly 0.9.3 → 0.9.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 +23 -6
- package/core-entity-config.cjs +1 -1
- package/core-entity-config.mjs +1 -1
- package/core-mappings-config.cjs +1 -1
- package/core-mappings-config.mjs +1 -1
- package/core-sync-engine.cjs +1 -1
- package/core-sync-engine.mjs +1 -1
- package/core-view-compiler.cjs +1 -1
- package/core-view-compiler.mjs +1 -1
- package/core.cjs +11 -6
- package/core.d.cts +2 -2
- package/core.d.mts +2 -2
- package/core.mjs +8 -7
- package/drizzle.cjs +74 -9
- package/drizzle.d.cts +83 -7
- package/drizzle.d.mts +83 -7
- package/drizzle.mjs +74 -10
- package/{entity-config-DtBsl2MQ.cjs → entity-config-7sGCk_nw.cjs} +1 -1
- package/{entity-config-kUHri9yA.mjs → entity-config-B74SPOUZ.mjs} +1 -1
- package/index.cjs +180 -21
- package/index.mjs +180 -21
- package/{internal-Df-PtOH_.mjs → internal-CykXpEGk.mjs} +2 -2
- package/{internal-CwnEG6Wl.cjs → internal-DqduZU3l.cjs} +2 -2
- package/internal-entity-config.cjs +1 -1
- package/internal-entity-config.mjs +1 -1
- package/internal-entity-heuristics.cjs +1 -1
- package/internal-entity-heuristics.mjs +1 -1
- package/internal-mappings-config.cjs +1 -1
- package/internal-mappings-config.mjs +1 -1
- package/{internal-CDY8g9Ic.d.mts → internal-o2M7FGlR.d.mts} +29 -1
- package/internal-sync-engine.cjs +1 -1
- package/internal-sync-engine.mjs +1 -1
- package/internal-view-compiler.cjs +1 -1
- package/internal-view-compiler.mjs +1 -1
- package/{internal-Da_dlcQ_.d.cts → internal-wCXSELDN.d.cts} +29 -1
- package/internal.cjs +11 -6
- package/internal.d.cts +2 -2
- package/internal.d.mts +2 -2
- package/internal.mjs +8 -7
- package/{mappings-config-B0RqFLxu.cjs → mappings-config-CLyEg0sf.cjs} +1 -1
- package/{mappings-config-B1b1HVBg.mjs → mappings-config-DQS_s4dg.mjs} +1 -1
- package/{namespace-probe-EYyxWZnf.d.cts → namespace-probe-BoGI18dQ.d.cts} +8 -0
- package/{namespace-probe-BmdGnYMn.d.mts → namespace-probe-Cn7voAd0.d.mts} +8 -0
- package/package.json +1 -1
- package/{projection-D7xmQP4S.mjs → projection-CSGJbZlZ.mjs} +25 -4
- package/{projection-C5244vGa.cjs → projection-Dz6_i1-w.cjs} +25 -4
- package/projection-sql-BgnfiGqF.mjs +65 -0
- package/projection-sql-Cl1qIn8m.cjs +94 -0
- package/{sdk-COLUHhJq.cjs → sdk-CM1UO-I7.cjs} +58 -15
- package/{sdk-CZNXBsgA.mjs → sdk-DYBDR939.mjs} +53 -16
- package/sdk.cjs +3 -2
- package/sdk.d.cts +5 -2
- package/sdk.d.mts +5 -2
- package/sdk.mjs +3 -3
- package/{sync-engine-abE94g6X.cjs → sync-engine-C4A5jgA4.cjs} +1 -1
- package/{sync-engine-SuxSzbZ4.mjs → sync-engine-Dt5QQSKO.mjs} +1 -1
- package/{view-compiler-Blu4OXJ7.cjs → view-compiler-B5nDal14.cjs} +21 -74
- package/{view-compiler-CQx8TRrW.mjs → view-compiler-CSYwEWf5.mjs} +2 -55
- package/vite.d.cts +1 -1
- package/vite.d.mts +1 -1
package/drizzle.mjs
CHANGED
|
@@ -1,6 +1,77 @@
|
|
|
1
|
+
import { t as BASE_REF } from "./projection-CSGJbZlZ.mjs";
|
|
1
2
|
import { t as DRIZZLE_META } from "./drizzle-meta-D-5k3n0i.mjs";
|
|
3
|
+
import { a as quoteIdent, n as compileProjectionIdentity, r as compileProjectionJoins, t as compileProjectionFrom } from "./projection-sql-BgnfiGqF.mjs";
|
|
2
4
|
import { n as buildUniqueConstraints, r as discoverFromDrizzle, t as buildColumnsMeta } from "./drizzle-3kvhkwO7.mjs";
|
|
3
5
|
import { asc, getTableColumns, getTableName, sql } from "drizzle-orm";
|
|
6
|
+
//#region ../sdk/src/drizzle-projection.ts
|
|
7
|
+
/** `<ref>.<column>` — the token `fields` carries and cloud-ingest reads. */
|
|
8
|
+
function refToken(ref) {
|
|
9
|
+
return `${ref.table}.${ref.column}`;
|
|
10
|
+
}
|
|
11
|
+
function refSql(ref) {
|
|
12
|
+
return `${quoteIdent(ref.table)}.${quoteIdent(ref.column)}`;
|
|
13
|
+
}
|
|
14
|
+
function parseRef(value) {
|
|
15
|
+
if (typeof value !== "string") return value;
|
|
16
|
+
const dot = value.indexOf(".");
|
|
17
|
+
if (dot <= 0 || dot === value.length - 1) throw new Error(`fromDrizzleProjection: cursorField "${value}" must be a ref token like "base.updated_at".`);
|
|
18
|
+
return {
|
|
19
|
+
table: value.slice(0, dot),
|
|
20
|
+
column: value.slice(dot + 1)
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Refs address tables symbolically — `base` or a join key — never by table name,
|
|
25
|
+
* which is what stops a projection reaching a table it did not declare. An
|
|
26
|
+
* unresolvable ref would otherwise compile to SQL referencing a missing alias
|
|
27
|
+
* and fail at the database with a message that never mentions the config.
|
|
28
|
+
*/
|
|
29
|
+
function assertKnownRef(ref, known, context) {
|
|
30
|
+
if (!known.has(ref.table)) throw new Error(`fromDrizzleProjection: ${context} names unknown ref "${ref.table}" — expected "${[...known].join("\", \"")}".`);
|
|
31
|
+
}
|
|
32
|
+
function normalizeRows(result) {
|
|
33
|
+
if (Array.isArray(result)) return result;
|
|
34
|
+
const rows = result.rows;
|
|
35
|
+
if (Array.isArray(rows)) return rows;
|
|
36
|
+
throw new Error("fromDrizzleProjection: db.execute() returned neither an array of rows nor { rows }.");
|
|
37
|
+
}
|
|
38
|
+
function fromDrizzleProjection(db, projection, opts) {
|
|
39
|
+
const client = db;
|
|
40
|
+
const known = /* @__PURE__ */ new Set([BASE_REF, ...projection.joins.map((j) => j.key)]);
|
|
41
|
+
if (projection.identity.kind === "base_pk" && projection.identity.column === void 0 && opts.basePkColumn === void 0) throw new Error(`fromDrizzleProjection: projection "${projection.entityKey}" has a base_pk identity with no column — pass { basePkColumn }.`);
|
|
42
|
+
const cursorRef = parseRef(opts.cursorField);
|
|
43
|
+
assertKnownRef(cursorRef, known, "cursorField");
|
|
44
|
+
for (const [field, binding] of Object.entries(projection.fieldBindings)) assertKnownRef(binding.from, known, `field "${field}"`);
|
|
45
|
+
const identityExpr = compileProjectionIdentity(projection, opts.basePkColumn ?? "");
|
|
46
|
+
const cursorToken = refToken(cursorRef);
|
|
47
|
+
const cursorExpr = `CAST(${refSql(cursorRef)} AS timestamptz)`;
|
|
48
|
+
const cursorRead = `to_char(${cursorExpr} AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')`;
|
|
49
|
+
const selectItems = [`${identityExpr} AS ${quoteIdent("id")}`, `${cursorRead} AS ${quoteIdent(cursorToken)}`];
|
|
50
|
+
const emitted = /* @__PURE__ */ new Set([cursorToken]);
|
|
51
|
+
for (const binding of Object.values(projection.fieldBindings)) {
|
|
52
|
+
const token = refToken(binding.from);
|
|
53
|
+
if (emitted.has(token)) continue;
|
|
54
|
+
emitted.add(token);
|
|
55
|
+
selectItems.push(`${refSql(binding.from)} AS ${quoteIdent(token)}`);
|
|
56
|
+
}
|
|
57
|
+
const head = [
|
|
58
|
+
`select ${selectItems.join(", ")}`,
|
|
59
|
+
compileProjectionFrom(projection),
|
|
60
|
+
...compileProjectionJoins(projection)
|
|
61
|
+
].join(" ");
|
|
62
|
+
return {
|
|
63
|
+
entity: opts.entity,
|
|
64
|
+
cursorField: cursorToken,
|
|
65
|
+
idField: "id",
|
|
66
|
+
...opts.objectType === void 0 ? {} : { objectType: opts.objectType },
|
|
67
|
+
fetchPage: async ({ since, limit }) => {
|
|
68
|
+
const where = since ? sql` where (${sql.raw(cursorExpr)}, ${sql.raw(identityExpr)}) > (${since.time.toISOString()}::timestamptz, ${since.id})` : sql``;
|
|
69
|
+
const query = sql`${sql.raw(head)}${where} order by ${sql.raw(cursorExpr)} asc, ${sql.raw(identityExpr)} asc limit ${limit}`;
|
|
70
|
+
return normalizeRows(await client.execute(query));
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
4
75
|
//#region ../sdk/src/drizzle.ts
|
|
5
76
|
function inferIdField(table) {
|
|
6
77
|
const columns = getTableColumns(table);
|
|
@@ -13,14 +84,6 @@ function inferIdField(table) {
|
|
|
13
84
|
const kind = primaryKeys.length === 0 ? "no" : "a composite";
|
|
14
85
|
throw new Error(`fromDrizzle: table "${getTableName(table)}" has ${kind} single-column primary key — pass { idField } explicitly.`);
|
|
15
86
|
}
|
|
16
|
-
/**
|
|
17
|
-
* Build a `SyncSource` from a Drizzle table, generating the keyset-paginated
|
|
18
|
-
* `fetchPage` the SDK needs. `cursorField`/`idField` are drizzle property keys;
|
|
19
|
-
* `idField` defaults to the table's single-column primary key.
|
|
20
|
-
*
|
|
21
|
-
* `drizzle-orm` is an optional peer dependency — installed only when you import
|
|
22
|
-
* `@clivly/sdk/drizzle`.
|
|
23
|
-
*/
|
|
24
87
|
function fromDrizzle(db, table, opts) {
|
|
25
88
|
const drizzleDb = db;
|
|
26
89
|
const idField = opts.idField ?? inferIdField(table);
|
|
@@ -37,7 +100,8 @@ function fromDrizzle(db, table, opts) {
|
|
|
37
100
|
entity: opts.entity,
|
|
38
101
|
cursorField: cursorColumnName,
|
|
39
102
|
idField: idColumnName,
|
|
40
|
-
fetchPage: ({ since, limit }) => drizzleDb.select(projection).from(table).where(since ? sql`(${cursorColumn}, ${idColumn}) > (${since.time}, ${since.id})` : void 0).orderBy(asc(cursorColumn), asc(idColumn)).limit(limit)
|
|
103
|
+
fetchPage: ({ since, limit }) => drizzleDb.select(projection).from(table).where(since ? sql`(${cursorColumn}, ${idColumn}) > (${since.time}, ${since.id})` : void 0).orderBy(asc(cursorColumn), asc(idColumn)).limit(limit),
|
|
104
|
+
...opts.objectType === void 0 ? {} : { objectType: opts.objectType }
|
|
41
105
|
};
|
|
42
106
|
const uniqueConstraints = buildUniqueConstraints(table);
|
|
43
107
|
const meta = {
|
|
@@ -74,4 +138,4 @@ function drizzleIntrospector(db) {
|
|
|
74
138
|
};
|
|
75
139
|
}
|
|
76
140
|
//#endregion
|
|
77
|
-
export { buildUniqueConstraints, discoverFromDrizzle, drizzleIntrospector, fromDrizzle };
|
|
141
|
+
export { buildUniqueConstraints, discoverFromDrizzle, drizzleIntrospector, fromDrizzle, fromDrizzleProjection };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as _enum, c as literal, d as strictObject, f as string, i as projectionSchema, l as number, o as array, p as union, r as identityRefs, s as boolean, t as BASE_REF, u as record } from "./projection-
|
|
1
|
+
import { a as _enum, c as literal, d as strictObject, f as string, i as projectionSchema, l as number, o as array, p as union, r as identityRefs, s as boolean, t as BASE_REF, u as record } from "./projection-CSGJbZlZ.mjs";
|
|
2
2
|
//#region ../core/src/schema-meta.ts
|
|
3
3
|
/**
|
|
4
4
|
* Accessors over a discovered table's schema-v2 metadata.
|
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-7sGCk_nw.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-CM1UO-I7.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");
|
|
@@ -292,10 +292,37 @@ function addCommand(pm, packages) {
|
|
|
292
292
|
};
|
|
293
293
|
}
|
|
294
294
|
//#endregion
|
|
295
|
+
//#region src/tick-route.ts
|
|
296
|
+
/**
|
|
297
|
+
* The tick route's public URL path, and the template `clivly login` writes for
|
|
298
|
+
* it.
|
|
299
|
+
*
|
|
300
|
+
* Every framework the CLI scaffolds mounts the tick handler at the same URL —
|
|
301
|
+
* the *files* differ (`app/api/clivly/tick/route.ts`,
|
|
302
|
+
* `src/routes/api/clivly/tick.ts`, `app/routes/api.clivly.tick.ts`, …) but each
|
|
303
|
+
* one resolves to `/api/clivly/tick`. So this is a constant, not a per-framework
|
|
304
|
+
* lookup, and `clivly dev`, the generated config and the `.env` template can all
|
|
305
|
+
* read from one place instead of repeating the string and drifting.
|
|
306
|
+
*/
|
|
307
|
+
const CLIVLY_TICK_PATH = "/api/clivly/tick";
|
|
308
|
+
/**
|
|
309
|
+
* What `clivly login` writes into `.env` for `CLIVLY_SYNC_TRIGGER_URL`.
|
|
310
|
+
*
|
|
311
|
+
* A visible template beats an empty line: the developer sees the shape they
|
|
312
|
+
* eventually need — a public origin plus the tick path — instead of an unhelpful
|
|
313
|
+
* `CLIVLY_SYNC_TRIGGER_URL=` they have to go and look up.
|
|
314
|
+
*
|
|
315
|
+
* It is safe to ship a truthy non-address here only because the SDK refuses to
|
|
316
|
+
* treat a bracketed value as configured (`isPlaceholderUrl` in
|
|
317
|
+
* `@clivly/sdk/trigger-url`). Change the brackets here and that guard stops
|
|
318
|
+
* matching, so the two must stay in step.
|
|
319
|
+
*/
|
|
320
|
+
const TRIGGER_URL_PLACEHOLDER = `https://<yourDomain.com>${CLIVLY_TICK_PATH}`;
|
|
321
|
+
//#endregion
|
|
295
322
|
//#region src/dev-tunnel.ts
|
|
296
323
|
const CLOUDFLARE_QUICK_TUNNEL_REGEX = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/;
|
|
297
324
|
const DEFAULT_PORT = "3000";
|
|
298
|
-
const DEFAULT_ROUTE_PATH =
|
|
325
|
+
const DEFAULT_ROUTE_PATH = CLIVLY_TICK_PATH;
|
|
299
326
|
function parseTunnelUrl(chunk) {
|
|
300
327
|
const match = chunk.match(CLOUDFLARE_QUICK_TUNNEL_REGEX);
|
|
301
328
|
return match ? match[0] : null;
|
|
@@ -947,7 +974,7 @@ function isKeySet(contents, key) {
|
|
|
947
974
|
function appendEnvKeys(existing, entries) {
|
|
948
975
|
const missing = entries.filter((e) => !hasKey$1(existing, e.key));
|
|
949
976
|
if (missing.length === 0) return existing;
|
|
950
|
-
const block = missing.map((e) => `${e.comment ? `# ${e.comment}\n` : ""}${e.key}
|
|
977
|
+
const block = missing.map((e) => `${e.comment ? `# ${e.comment}\n` : ""}${e.key}=${e.value ?? ""}`).join("\n");
|
|
951
978
|
return `${existing}${existing.length > 0 && !existing.endsWith("\n") ? "\n" : ""}${existing.length > 0 ? "\n" : ""}# Clivly\n${block}\n`;
|
|
952
979
|
}
|
|
953
980
|
/**
|
|
@@ -1400,15 +1427,23 @@ async function confirmWorkspace(deps, credentials) {
|
|
|
1400
1427
|
return await deps.confirm("Write this workspace's credentials to .env? [y/N] ");
|
|
1401
1428
|
}
|
|
1402
1429
|
function credentialEntries(credentials) {
|
|
1403
|
-
return [
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1430
|
+
return [
|
|
1431
|
+
{
|
|
1432
|
+
key: SECRET_KEY_ENV,
|
|
1433
|
+
value: credentials.apiKey,
|
|
1434
|
+
comment: `Org secret key — ${CREDENTIALS_LOCATION}`
|
|
1435
|
+
},
|
|
1436
|
+
{
|
|
1437
|
+
key: "CLIVLY_SYNC_TRIGGER_SECRET",
|
|
1438
|
+
value: credentials.triggerSecret,
|
|
1439
|
+
comment: `Signing secret — ${SYNC_SECRET_LOCATION}`
|
|
1440
|
+
},
|
|
1441
|
+
{
|
|
1442
|
+
key: "CLIVLY_SYNC_TRIGGER_URL",
|
|
1443
|
+
value: TRIGGER_URL_PLACEHOLDER,
|
|
1444
|
+
comment: "Public URL of your tick route — replace <yourDomain.com> once deployed. Local dev: run `clivly dev` instead."
|
|
1445
|
+
}
|
|
1446
|
+
];
|
|
1412
1447
|
}
|
|
1413
1448
|
function writeCredentials(options, credentials) {
|
|
1414
1449
|
const { deps } = options;
|
|
@@ -1775,7 +1810,8 @@ const CLIVLY_ENV_KEYS = [
|
|
|
1775
1810
|
},
|
|
1776
1811
|
{
|
|
1777
1812
|
key: "CLIVLY_SYNC_TRIGGER_URL",
|
|
1778
|
-
|
|
1813
|
+
value: TRIGGER_URL_PLACEHOLDER,
|
|
1814
|
+
comment: "Public URL of your tick route — replace <yourDomain.com> once deployed. Local dev: run `clivly dev` instead."
|
|
1779
1815
|
},
|
|
1780
1816
|
{
|
|
1781
1817
|
key: "CLIVLY_SYNC_TRIGGER_SECRET",
|
|
@@ -1873,7 +1909,13 @@ export default createClivlySDK({
|
|
|
1873
1909
|
${sources.join(",\n")},
|
|
1874
1910
|
},
|
|
1875
1911
|
syncTrigger: {
|
|
1876
|
-
//
|
|
1912
|
+
// Where Clivly Cloud calls to start a sync — your mounted tick route.
|
|
1913
|
+
//
|
|
1914
|
+
// \`path\` lets the SDK build its own public URL from the hosting platform's
|
|
1915
|
+
// environment (VERCEL_URL, CF_PAGES_URL, RAILWAY_PUBLIC_DOMAIN,
|
|
1916
|
+
// RENDER_EXTERNAL_URL, FLY_APP_NAME), so on those platforms you never have
|
|
1917
|
+
// to set the URL by hand. \`url\` overrides that when you do.
|
|
1918
|
+
path: "${CLIVLY_TICK_PATH}",
|
|
1877
1919
|
url: process.env.CLIVLY_SYNC_TRIGGER_URL,
|
|
1878
1920
|
secret: process.env.CLIVLY_SYNC_TRIGGER_SECRET,
|
|
1879
1921
|
},
|
|
@@ -2700,6 +2742,103 @@ function ladderExitCode(results) {
|
|
|
2700
2742
|
return results.every((result) => result.state !== "fail" && result.state !== "pending") ? 0 : 1;
|
|
2701
2743
|
}
|
|
2702
2744
|
//#endregion
|
|
2745
|
+
//#region src/boot-hook-check.ts
|
|
2746
|
+
const REMIX_ENTRY_CANDIDATES = [
|
|
2747
|
+
"app/entry.server.tsx",
|
|
2748
|
+
"app/entry.server.ts",
|
|
2749
|
+
"app/entry.server.jsx",
|
|
2750
|
+
"app/entry.server.js"
|
|
2751
|
+
];
|
|
2752
|
+
const NEXT_CONFIG_CANDIDATES = [
|
|
2753
|
+
"next.config.ts",
|
|
2754
|
+
"next.config.mjs",
|
|
2755
|
+
"next.config.js",
|
|
2756
|
+
"next.config.cjs",
|
|
2757
|
+
"next.config.mts"
|
|
2758
|
+
];
|
|
2759
|
+
const CLIVLY_MENTION = /clivly/i;
|
|
2760
|
+
const INSTRUMENTATION_HOOK_ENABLED = /instrumentationHook\s*:\s*true/;
|
|
2761
|
+
/**
|
|
2762
|
+
* Where this framework's boot hook lives, most likely location first.
|
|
2763
|
+
*
|
|
2764
|
+
* The file-owning frameworks are asked of `boot-hook.ts` rather than restated,
|
|
2765
|
+
* so a path change there cannot leave this check hunting for a file `init` no
|
|
2766
|
+
* longer writes. The other two are patched into a file the app already owns.
|
|
2767
|
+
*/
|
|
2768
|
+
function bootSiteCandidates(framework) {
|
|
2769
|
+
const owned = [bootHookPath(framework, "app"), bootHookPath(framework, "src/app")].filter((path) => path !== null);
|
|
2770
|
+
if (owned.length > 0) return [...new Set(owned)];
|
|
2771
|
+
if (framework === "tanstack-start") return [...VITE_CONFIG_CANDIDATES];
|
|
2772
|
+
if (framework === "remix") return REMIX_ENTRY_CANDIDATES;
|
|
2773
|
+
return [];
|
|
2774
|
+
}
|
|
2775
|
+
function declaredDeps(readFile) {
|
|
2776
|
+
const raw = readFile("package.json");
|
|
2777
|
+
if (raw === null) return null;
|
|
2778
|
+
try {
|
|
2779
|
+
const pkg = JSON.parse(raw);
|
|
2780
|
+
return {
|
|
2781
|
+
...pkg.dependencies,
|
|
2782
|
+
...pkg.devDependencies
|
|
2783
|
+
};
|
|
2784
|
+
} catch {
|
|
2785
|
+
return null;
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
function findBootSite(readFile, framework) {
|
|
2789
|
+
for (const candidate of bootSiteCandidates(framework)) {
|
|
2790
|
+
const source = readFile(candidate);
|
|
2791
|
+
if (source !== null && CLIVLY_MENTION.test(source)) return candidate;
|
|
2792
|
+
}
|
|
2793
|
+
return null;
|
|
2794
|
+
}
|
|
2795
|
+
/**
|
|
2796
|
+
* On Next.js below 15 the hook file is ignored unless the flag is on, so a
|
|
2797
|
+
* present-and-correct instrumentation.ts is not evidence of anything. A missing
|
|
2798
|
+
* next.config is the same failure as one without the flag: either way nothing
|
|
2799
|
+
* switches the hook on.
|
|
2800
|
+
*/
|
|
2801
|
+
function nextFlagVerdict(readFile, version, site) {
|
|
2802
|
+
if (!nextNeedsInstrumentationFlag(version)) return null;
|
|
2803
|
+
for (const candidate of NEXT_CONFIG_CANDIDATES) {
|
|
2804
|
+
const source = readFile(candidate);
|
|
2805
|
+
if (source !== null && INSTRUMENTATION_HOOK_ENABLED.test(source)) return null;
|
|
2806
|
+
}
|
|
2807
|
+
return {
|
|
2808
|
+
state: "broken",
|
|
2809
|
+
detail: `${site} exists, but Next.js ${version} ignores it — experimental.instrumentationHook is not enabled in next.config.`,
|
|
2810
|
+
hint: NEXT_INSTRUMENTATION_FLAG_STEP
|
|
2811
|
+
};
|
|
2812
|
+
}
|
|
2813
|
+
function checkBootHook(readFile) {
|
|
2814
|
+
const deps = declaredDeps(readFile);
|
|
2815
|
+
if (deps === null) return {
|
|
2816
|
+
state: "unknown",
|
|
2817
|
+
detail: "package.json not readable — cannot tell how this app boots."
|
|
2818
|
+
};
|
|
2819
|
+
const framework = detectFramework(deps);
|
|
2820
|
+
if (framework === "unknown") return {
|
|
2821
|
+
state: "unknown",
|
|
2822
|
+
detail: "No supported framework detected, so there is no known place to check."
|
|
2823
|
+
};
|
|
2824
|
+
const label = FRAMEWORK_LABELS[framework];
|
|
2825
|
+
const site = findBootSite(readFile, framework);
|
|
2826
|
+
if (site === null) return {
|
|
2827
|
+
state: "broken",
|
|
2828
|
+
detail: `No Clivly boot hook found in this ${label} app.`,
|
|
2829
|
+
hint: `Nothing starts the SDK, so your app never tells Clivly it is running — the dashboard stays silent with no error anywhere. Run \`clivly init\` to scaffold it, or call \`clivly.start()\` where your server boots (${bootSiteCandidates(framework)[0]}).`
|
|
2830
|
+
};
|
|
2831
|
+
const nextVersion = deps.next;
|
|
2832
|
+
if (framework === "nextjs" && nextVersion) {
|
|
2833
|
+
const broken = nextFlagVerdict(readFile, nextVersion, site);
|
|
2834
|
+
if (broken) return broken;
|
|
2835
|
+
}
|
|
2836
|
+
return {
|
|
2837
|
+
state: "ok",
|
|
2838
|
+
detail: site
|
|
2839
|
+
};
|
|
2840
|
+
}
|
|
2841
|
+
//#endregion
|
|
2703
2842
|
//#region src/edge-safety.ts
|
|
2704
2843
|
const NODE_BUILTINS = /* @__PURE__ */ new Set([
|
|
2705
2844
|
"assert",
|
|
@@ -2886,7 +3025,7 @@ function pingOnce(ctx) {
|
|
|
2886
3025
|
}
|
|
2887
3026
|
const REASON_HINT = {
|
|
2888
3027
|
ok: "",
|
|
2889
|
-
invalid_api_key: "The key was rejected (401).
|
|
3028
|
+
invalid_api_key: "The key was rejected (401). Run `clivly login` to re-pair and rewrite the credentials, and check CLIVLY_SECRET_KEY (or legacy CLIVLY_API_KEY) belongs to the org whose dashboard you are watching.",
|
|
2890
3029
|
forbidden: "The key is valid but lacks access to this org (403).",
|
|
2891
3030
|
rate_limited: "Rate limited (429). Wait a moment and retry.",
|
|
2892
3031
|
server_error: "Clivly returned a server error (5xx). Retry shortly.",
|
|
@@ -2986,14 +3125,15 @@ const triggerUrlStage = {
|
|
|
2986
3125
|
needs: [configStage.id],
|
|
2987
3126
|
run(ctx) {
|
|
2988
3127
|
const url = ctx.sdk?.syncTrigger?.url;
|
|
2989
|
-
if (url) return Promise.resolve({
|
|
3128
|
+
if (url && !require_sdk.isPlaceholderUrl(url)) return Promise.resolve({
|
|
2990
3129
|
state: "pass",
|
|
2991
3130
|
detail: url
|
|
2992
3131
|
});
|
|
3132
|
+
const isPlaceholder = url !== void 0;
|
|
2993
3133
|
return Promise.resolve({
|
|
2994
3134
|
state: "warn",
|
|
2995
|
-
detail: "Not set — remote sync cannot call back into this app.",
|
|
2996
|
-
hint: "
|
|
3135
|
+
detail: isPlaceholder ? `Still the placeholder (${url}) — remote sync cannot call back into this app.` : "Not set — remote sync cannot call back into this app.",
|
|
3136
|
+
hint: "Replace <yourDomain.com> with your deployed origin, or delete the line — on Vercel, Cloudflare Pages, Railway, Render and Fly the SDK derives the URL from the platform. For local development run `clivly dev`: it opens a tunnel and registers the URL for you, outranking whatever is in .env. Push-only setups can ignore this."
|
|
2997
3137
|
});
|
|
2998
3138
|
}
|
|
2999
3139
|
};
|
|
@@ -3041,6 +3181,24 @@ const STATUS_STAGES = [
|
|
|
3041
3181
|
});
|
|
3042
3182
|
}
|
|
3043
3183
|
},
|
|
3184
|
+
{
|
|
3185
|
+
id: "boot-hook",
|
|
3186
|
+
label: "Boot hook wired",
|
|
3187
|
+
run(ctx) {
|
|
3188
|
+
const read = ctx.readConfigSource ?? defaultReadConfigSource;
|
|
3189
|
+
const root = (0, node_path.dirname)(ctx.configPath);
|
|
3190
|
+
const verdict = checkBootHook((relative) => read((0, node_path.resolve)(root, relative)));
|
|
3191
|
+
if (verdict.state === "ok") return Promise.resolve({
|
|
3192
|
+
state: "pass",
|
|
3193
|
+
detail: `starts Clivly from ${verdict.detail}`
|
|
3194
|
+
});
|
|
3195
|
+
return Promise.resolve({
|
|
3196
|
+
state: verdict.state === "unknown" ? "skip" : "fail",
|
|
3197
|
+
detail: verdict.detail,
|
|
3198
|
+
hint: verdict.hint
|
|
3199
|
+
});
|
|
3200
|
+
}
|
|
3201
|
+
},
|
|
3044
3202
|
sdkChecksStage,
|
|
3045
3203
|
triggerUrlStage
|
|
3046
3204
|
];
|
|
@@ -3094,6 +3252,7 @@ const ALIAS_STAGE_IDS = {
|
|
|
3094
3252
|
doctor: [
|
|
3095
3253
|
"config",
|
|
3096
3254
|
"runtime",
|
|
3255
|
+
"boot-hook",
|
|
3097
3256
|
"setup-checks"
|
|
3098
3257
|
],
|
|
3099
3258
|
connect: [
|
|
@@ -3547,8 +3706,8 @@ function previewMutations(deps, mutations, options) {
|
|
|
3547
3706
|
function printNextSteps(out, authComplete, loginRan, envFile) {
|
|
3548
3707
|
out("\nNext steps:\n");
|
|
3549
3708
|
out(loginRan ? " 1. Credentials written by `clivly login`\n" : ` 1. Add CLIVLY_SECRET_KEY to ${envFile} — create one at\n ${CREDENTIALS_LOCATION}\n (the legacy CLIVLY_API_KEY is still accepted)\n`);
|
|
3550
|
-
out(" 2.
|
|
3551
|
-
out("
|
|
3709
|
+
out(" 2. Local dev: run `clivly dev` for dashboard-triggered sync\n");
|
|
3710
|
+
out(" It tunnels your app and registers the URL for you — remote sync is\n deploy-only otherwise, so nothing is broken if you skip it. Once deployed\n to Vercel/Cloudflare/Railway/Render/Fly the URL is detected automatically;\n set CLIVLY_SYNC_TRIGGER_URL by hand only for a custom domain.\n");
|
|
3552
3711
|
const step = authComplete ? 3 : 4;
|
|
3553
3712
|
if (!authComplete) out(" 3. Finish resolveUser in the auth/verify route\n");
|
|
3554
3713
|
out(` ${step}. Run \`clivly status\` to see where you are\n`);
|