clivly 0.9.2 → 0.9.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/README.md +60 -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 +3 -2
- package/core.d.mts +3 -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 +141 -29
- package/index.d.cts +2 -1
- package/index.d.mts +2 -1
- package/index.mjs +141 -30
- package/integration-state-BK-riZnm.d.cts +130 -0
- package/integration-state-BK-riZnm.d.mts +130 -0
- 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 +3 -2
- package/internal.d.mts +3 -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-C6aHjedO.d.cts → namespace-probe-BoGI18dQ.d.cts} +15 -0
- package/{namespace-probe-Dj-0Q3Hb.d.mts → namespace-probe-Cn7voAd0.d.mts} +15 -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-D4Wp_uxb.cjs → sdk-CM1UO-I7.cjs} +59 -15
- package/{sdk-DXV8M_Io.mjs → sdk-DYBDR939.mjs} +54 -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;
|
|
@@ -1002,7 +1029,7 @@ Usage:
|
|
|
1002
1029
|
clivly init [options] Scaffold Clivly (config, routes, env file) for your app
|
|
1003
1030
|
clivly add widget [options] Scaffold the chat-session route
|
|
1004
1031
|
clivly status [options] Check your setup, step by step
|
|
1005
|
-
clivly push-schema [options]
|
|
1032
|
+
clivly push-schema [options] Push schema, custom objects and mappings to Clivly
|
|
1006
1033
|
clivly dev [options] Tunnel localhost so remote sync can reach you
|
|
1007
1034
|
clivly login [options] Pair this machine and write credentials to your env file
|
|
1008
1035
|
clivly --version Print the installed clivly version
|
|
@@ -1058,7 +1085,9 @@ Options:
|
|
|
1058
1085
|
Usage:
|
|
1059
1086
|
clivly status [--verbose] [--config <path>] [--env-file <path>]
|
|
1060
1087
|
|
|
1061
|
-
|
|
1088
|
+
Prints two sections. "Workspace" is how far setup has got on Clivly's side,
|
|
1089
|
+
computed by the server so the terminal and the dashboard agree. "This project"
|
|
1090
|
+
runs the local ladder: credential, reachability, config load, edge-safety,
|
|
1062
1091
|
setup checks, sync trigger. Read-only — writes nothing.
|
|
1063
1092
|
|
|
1064
1093
|
Options:
|
|
@@ -1066,13 +1095,23 @@ Options:
|
|
|
1066
1095
|
--config <path> Path to clivly.config.ts
|
|
1067
1096
|
--env-file <path> Extra env file to load
|
|
1068
1097
|
`,
|
|
1069
|
-
"push-schema": `clivly push-schema —
|
|
1098
|
+
"push-schema": `clivly push-schema — push everything your config declares
|
|
1070
1099
|
|
|
1071
1100
|
Usage:
|
|
1072
1101
|
clivly push-schema [--config <path>]
|
|
1073
1102
|
|
|
1074
|
-
|
|
1075
|
-
|
|
1103
|
+
Pushes three things to Clivly, without deploying your app:
|
|
1104
|
+
1. Your table and column NAMES, so the dashboard can offer them for mapping.
|
|
1105
|
+
Never row data — discovery is names only.
|
|
1106
|
+
2. The custom object types you declared with \`objectType\`.
|
|
1107
|
+
3. The mappings that make those objects actually receive rows.
|
|
1108
|
+
|
|
1109
|
+
Reads your config; writes nothing locally. Safe to re-run — it reports what it
|
|
1110
|
+
created, updated, or left alone.
|
|
1111
|
+
|
|
1112
|
+
Exits 1 if Clivly refuses your config, most often because an entity would
|
|
1113
|
+
overwrite a mapping edited in the dashboard. That makes it usable in CI as a
|
|
1114
|
+
schema gate.
|
|
1076
1115
|
`,
|
|
1077
1116
|
dev: `clivly dev — tunnel localhost so remote sync can reach you
|
|
1078
1117
|
|
|
@@ -1388,15 +1427,23 @@ async function confirmWorkspace(deps, credentials) {
|
|
|
1388
1427
|
return await deps.confirm("Write this workspace's credentials to .env? [y/N] ");
|
|
1389
1428
|
}
|
|
1390
1429
|
function credentialEntries(credentials) {
|
|
1391
|
-
return [
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
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
|
+
];
|
|
1400
1447
|
}
|
|
1401
1448
|
function writeCredentials(options, credentials) {
|
|
1402
1449
|
const { deps } = options;
|
|
@@ -1861,7 +1908,13 @@ export default createClivlySDK({
|
|
|
1861
1908
|
${sources.join(",\n")},
|
|
1862
1909
|
},
|
|
1863
1910
|
syncTrigger: {
|
|
1864
|
-
//
|
|
1911
|
+
// Where Clivly Cloud calls to start a sync — your mounted tick route.
|
|
1912
|
+
//
|
|
1913
|
+
// \`path\` lets the SDK build its own public URL from the hosting platform's
|
|
1914
|
+
// environment (VERCEL_URL, CF_PAGES_URL, RAILWAY_PUBLIC_DOMAIN,
|
|
1915
|
+
// RENDER_EXTERNAL_URL, FLY_APP_NAME), so on those platforms you never have
|
|
1916
|
+
// to set the URL by hand. \`url\` overrides that when you do.
|
|
1917
|
+
path: "${CLIVLY_TICK_PATH}",
|
|
1865
1918
|
url: process.env.CLIVLY_SYNC_TRIGGER_URL,
|
|
1866
1919
|
secret: process.env.CLIVLY_SYNC_TRIGGER_SECRET,
|
|
1867
1920
|
},
|
|
@@ -2861,6 +2914,17 @@ function defaultReadConfigSource(path) {
|
|
|
2861
2914
|
return null;
|
|
2862
2915
|
}
|
|
2863
2916
|
}
|
|
2917
|
+
/**
|
|
2918
|
+
* Ping Clivly at most once per command, memoised on the context.
|
|
2919
|
+
*
|
|
2920
|
+
* `clivly status` asks two questions of one response — "did this call reach
|
|
2921
|
+
* Clivly" and "how far has this workspace got" — and they must come from the
|
|
2922
|
+
* same answer, not two calls that could disagree.
|
|
2923
|
+
*/
|
|
2924
|
+
function pingOnce(ctx) {
|
|
2925
|
+
ctx.pingResult ??= ctx.connect(ctx.apiKey, ctx.apiUrl);
|
|
2926
|
+
return ctx.pingResult;
|
|
2927
|
+
}
|
|
2864
2928
|
const REASON_HINT = {
|
|
2865
2929
|
ok: "",
|
|
2866
2930
|
invalid_api_key: "The key was rejected (401). Check CLIVLY_SECRET_KEY (or legacy CLIVLY_API_KEY).",
|
|
@@ -2872,7 +2936,7 @@ const REASON_HINT = {
|
|
|
2872
2936
|
network_error: "Could not reach Clivly. Check your network or --api-url."
|
|
2873
2937
|
};
|
|
2874
2938
|
const apiKeyStage = {
|
|
2875
|
-
id: "
|
|
2939
|
+
id: "api-key",
|
|
2876
2940
|
label: "API key present",
|
|
2877
2941
|
run(ctx) {
|
|
2878
2942
|
if (ctx.apiKey) return Promise.resolve({
|
|
@@ -2887,11 +2951,11 @@ const apiKeyStage = {
|
|
|
2887
2951
|
}
|
|
2888
2952
|
};
|
|
2889
2953
|
const reachabilityStage = {
|
|
2890
|
-
id: "
|
|
2954
|
+
id: "reachable",
|
|
2891
2955
|
label: "Clivly reachable",
|
|
2892
2956
|
needs: [apiKeyStage.id],
|
|
2893
2957
|
async run(ctx) {
|
|
2894
|
-
const result = await
|
|
2958
|
+
const result = await pingOnce(ctx);
|
|
2895
2959
|
if (result.ok) {
|
|
2896
2960
|
const name = result.org?.name ?? "(unnamed org)";
|
|
2897
2961
|
const id = result.org?.id ? ` (${result.org.id})` : "";
|
|
@@ -2963,14 +3027,15 @@ const triggerUrlStage = {
|
|
|
2963
3027
|
needs: [configStage.id],
|
|
2964
3028
|
run(ctx) {
|
|
2965
3029
|
const url = ctx.sdk?.syncTrigger?.url;
|
|
2966
|
-
if (url) return Promise.resolve({
|
|
3030
|
+
if (url && !require_sdk.isPlaceholderUrl(url)) return Promise.resolve({
|
|
2967
3031
|
state: "pass",
|
|
2968
3032
|
detail: url
|
|
2969
3033
|
});
|
|
3034
|
+
const isPlaceholder = url !== void 0;
|
|
2970
3035
|
return Promise.resolve({
|
|
2971
3036
|
state: "warn",
|
|
2972
|
-
detail: "Not set — remote sync cannot call back into this app.",
|
|
2973
|
-
hint: "
|
|
3037
|
+
detail: isPlaceholder ? `Still the placeholder (${url}) — remote sync cannot call back into this app.` : "Not set — remote sync cannot call back into this app.",
|
|
3038
|
+
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."
|
|
2974
3039
|
});
|
|
2975
3040
|
}
|
|
2976
3041
|
};
|
|
@@ -3025,19 +3090,57 @@ function defaultLoadSdk(path) {
|
|
|
3025
3090
|
return loadConfigSdk(path, "doctor");
|
|
3026
3091
|
}
|
|
3027
3092
|
//#endregion
|
|
3093
|
+
//#region src/workspace-section.ts
|
|
3094
|
+
const MARK$1 = {
|
|
3095
|
+
active: "⏳",
|
|
3096
|
+
blocked: "🔒",
|
|
3097
|
+
done: "✅",
|
|
3098
|
+
failed: "❌",
|
|
3099
|
+
skipped: "⏭️ "
|
|
3100
|
+
};
|
|
3101
|
+
/**
|
|
3102
|
+
* The workspace half of `clivly status`, rendered from what the server sent.
|
|
3103
|
+
*
|
|
3104
|
+
* This module decides nothing about a stage's state — it prints the verdict the
|
|
3105
|
+
* server computed. A CLI that computed its own would be a second ladder, which
|
|
3106
|
+
* is the drift this whole change removes.
|
|
3107
|
+
*
|
|
3108
|
+
* "Unavailable" and "nothing is set up" are deliberately different outcomes. An
|
|
3109
|
+
* old server, a rejected key and a dead network all mean we could not ask; none
|
|
3110
|
+
* of them means the workspace is empty.
|
|
3111
|
+
*/
|
|
3112
|
+
function workspaceSection(result) {
|
|
3113
|
+
if (!result) return {
|
|
3114
|
+
kind: "unavailable",
|
|
3115
|
+
reason: "Could not reach Clivly."
|
|
3116
|
+
};
|
|
3117
|
+
if (!result.ok) return {
|
|
3118
|
+
kind: "unavailable",
|
|
3119
|
+
reason: result.status === null ? "Could not reach Clivly (no response)." : `Clivly rejected the request — ${result.reason} (HTTP ${result.status}).`
|
|
3120
|
+
};
|
|
3121
|
+
if (!result.workspace) return {
|
|
3122
|
+
kind: "unavailable",
|
|
3123
|
+
reason: "This Clivly server does not report setup progress. Upgrade the server to see it here."
|
|
3124
|
+
};
|
|
3125
|
+
return {
|
|
3126
|
+
kind: "ladder",
|
|
3127
|
+
lines: result.workspace.steps.map((step) => ` ${MARK$1[step.state]} ${step.label}${step.evidence ? ` ${step.evidence}` : ""}`)
|
|
3128
|
+
};
|
|
3129
|
+
}
|
|
3130
|
+
//#endregion
|
|
3028
3131
|
//#region src/status.ts
|
|
3029
3132
|
/** Stage ids each legacy command maps onto. */
|
|
3030
3133
|
const ALIAS_STAGE_IDS = {
|
|
3031
3134
|
status: STATUS_STAGES.map((stage) => stage.id),
|
|
3032
|
-
ping: ["
|
|
3135
|
+
ping: ["api-key", "reachable"],
|
|
3033
3136
|
doctor: [
|
|
3034
3137
|
"config",
|
|
3035
3138
|
"runtime",
|
|
3036
3139
|
"setup-checks"
|
|
3037
3140
|
],
|
|
3038
3141
|
connect: [
|
|
3039
|
-
"
|
|
3040
|
-
"
|
|
3142
|
+
"api-key",
|
|
3143
|
+
"reachable",
|
|
3041
3144
|
"config",
|
|
3042
3145
|
"trigger-url"
|
|
3043
3146
|
]
|
|
@@ -3093,6 +3196,13 @@ async function runStatus(options) {
|
|
|
3093
3196
|
};
|
|
3094
3197
|
out("\nClivly status\n");
|
|
3095
3198
|
const results = await runLadder(stages, ctx);
|
|
3199
|
+
if (selected.has("reachable")) {
|
|
3200
|
+
const section = workspaceSection(ctx.pingResult ? await ctx.pingResult : null);
|
|
3201
|
+
out("\nWorkspace\n");
|
|
3202
|
+
if (section.kind === "ladder") for (const line of section.lines) out(`${line}\n`);
|
|
3203
|
+
else out(` ⚠️ ${section.reason}\n`);
|
|
3204
|
+
}
|
|
3205
|
+
out("\nThis project\n");
|
|
3096
3206
|
for (const result of results) renderStage(result, options.verbose ?? false, out);
|
|
3097
3207
|
const code = ladderExitCode(results);
|
|
3098
3208
|
if (code !== 0 && results.some((result) => result.state === "pending")) out("\nSetup is incomplete — finish the steps above and re-run.\n");
|
|
@@ -3122,6 +3232,7 @@ const KNOWN_COMMANDS = [
|
|
|
3122
3232
|
"init",
|
|
3123
3233
|
"status",
|
|
3124
3234
|
"push-schema",
|
|
3235
|
+
"add",
|
|
3125
3236
|
"dev",
|
|
3126
3237
|
"login",
|
|
3127
3238
|
"help",
|
|
@@ -3478,8 +3589,8 @@ function previewMutations(deps, mutations, options) {
|
|
|
3478
3589
|
function printNextSteps(out, authComplete, loginRan, envFile) {
|
|
3479
3590
|
out("\nNext steps:\n");
|
|
3480
3591
|
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`);
|
|
3481
|
-
out(" 2.
|
|
3482
|
-
out("
|
|
3592
|
+
out(" 2. Local dev: run `clivly dev` for dashboard-triggered sync\n");
|
|
3593
|
+
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");
|
|
3483
3594
|
const step = authComplete ? 3 : 4;
|
|
3484
3595
|
if (!authComplete) out(" 3. Finish resolveUser in the auth/verify route\n");
|
|
3485
3596
|
out(` ${step}. Run \`clivly status\` to see where you are\n`);
|
|
@@ -3889,6 +4000,7 @@ if (isEntrypoint(process.argv[1], require("url").pathToFileURL(__filename).href)
|
|
|
3889
4000
|
process.exitCode = code;
|
|
3890
4001
|
});
|
|
3891
4002
|
//#endregion
|
|
4003
|
+
exports.KNOWN_COMMANDS = KNOWN_COMMANDS;
|
|
3892
4004
|
exports.credentialsMissing = credentialsMissing;
|
|
3893
4005
|
exports.main = main;
|
|
3894
4006
|
exports.nearestCommand = nearestCommand;
|
package/index.d.cts
CHANGED
|
@@ -58,6 +58,7 @@ interface ProbeHit {
|
|
|
58
58
|
}
|
|
59
59
|
//#endregion
|
|
60
60
|
//#region src/index.d.ts
|
|
61
|
+
declare const KNOWN_COMMANDS: readonly ["init", "status", "push-schema", "add", "dev", "login", "help", "ping", "doctor", "connect"];
|
|
61
62
|
declare function nearestCommand(input: string): string | undefined;
|
|
62
63
|
interface AddDeps {
|
|
63
64
|
fileExists: (path: string) => boolean;
|
|
@@ -117,4 +118,4 @@ declare function runInit(options: RunInitOptions): Promise<number>;
|
|
|
117
118
|
declare function runInitFromArgv(argv: string[], cwd: string): Promise<number>;
|
|
118
119
|
declare function main(argv: string[]): Promise<number>;
|
|
119
120
|
//#endregion
|
|
120
|
-
export { AddDeps, RunInitOptions, credentialsMissing, main, nearestCommand, runAdd, runInit, runInitFromArgv };
|
|
121
|
+
export { AddDeps, KNOWN_COMMANDS, RunInitOptions, credentialsMissing, main, nearestCommand, runAdd, runInit, runInitFromArgv };
|
package/index.d.mts
CHANGED
|
@@ -58,6 +58,7 @@ interface ProbeHit {
|
|
|
58
58
|
}
|
|
59
59
|
//#endregion
|
|
60
60
|
//#region src/index.d.ts
|
|
61
|
+
declare const KNOWN_COMMANDS: readonly ["init", "status", "push-schema", "add", "dev", "login", "help", "ping", "doctor", "connect"];
|
|
61
62
|
declare function nearestCommand(input: string): string | undefined;
|
|
62
63
|
interface AddDeps {
|
|
63
64
|
fileExists: (path: string) => boolean;
|
|
@@ -117,4 +118,4 @@ declare function runInit(options: RunInitOptions): Promise<number>;
|
|
|
117
118
|
declare function runInitFromArgv(argv: string[], cwd: string): Promise<number>;
|
|
118
119
|
declare function main(argv: string[]): Promise<number>;
|
|
119
120
|
//#endregion
|
|
120
|
-
export { AddDeps, RunInitOptions, credentialsMissing, main, nearestCommand, runAdd, runInit, runInitFromArgv };
|
|
121
|
+
export { AddDeps, KNOWN_COMMANDS, RunInitOptions, credentialsMissing, main, nearestCommand, runAdd, runInit, runInitFromArgv };
|