clivly 0.9.0 → 0.9.2
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 +19 -7
- package/core-drizzle.d.cts +1 -1
- package/core-drizzle.d.mts +1 -1
- package/core-entity-config.cjs +3 -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 +2 -1
- package/core-mappings-config.d.cts +2 -2
- package/core-mappings-config.d.mts +2 -2
- package/core-mappings-config.mjs +2 -2
- package/core-sync-engine.cjs +1 -1
- package/core-sync-engine.d.cts +1 -1
- package/core-sync-engine.d.mts +1 -1
- package/core-sync-engine.mjs +1 -1
- package/core-view-compiler.cjs +2 -1
- package/core-view-compiler.d.cts +2 -2
- package/core-view-compiler.d.mts +2 -2
- package/core-view-compiler.mjs +2 -2
- package/core.cjs +5 -5
- package/core.d.cts +6 -6
- package/core.d.mts +6 -6
- package/core.mjs +5 -5
- package/{drizzle-BhN5M9zt.d.cts → drizzle-CiK8MMEZ.d.mts} +1 -1
- package/{drizzle-kBULBAOa.d.mts → drizzle-DEtRrqtc.d.cts} +1 -1
- package/drizzle.d.cts +2 -2
- package/drizzle.d.mts +2 -2
- package/{entity-config-C4Yf_fPj.d.cts → entity-config-B2RA53nw.d.cts} +168 -2
- package/{entity-config-C4Yf_fPj.d.mts → entity-config-B2RA53nw.d.mts} +168 -2
- package/{entity-config-CsLl-ftC.cjs → entity-config-DtBsl2MQ.cjs} +88 -13
- package/{entity-config-D3LmzgLb.mjs → entity-config-kUHri9yA.mjs} +77 -14
- package/index.cjs +375 -29
- package/index.d.cts +1 -0
- package/index.d.mts +1 -0
- package/index.mjs +375 -29
- package/{internal-MBkNo3bw.d.mts → internal-CDY8g9Ic.d.mts} +1 -2
- package/{internal-BARjyN4n.cjs → internal-CwnEG6Wl.cjs} +1 -1
- package/{internal-B8ydhsKd.d.cts → internal-Da_dlcQ_.d.cts} +1 -2
- package/{internal-DMySVbM0.mjs → internal-Df-PtOH_.mjs} +1 -1
- package/internal-drizzle.d.cts +1 -1
- package/internal-drizzle.d.mts +1 -1
- package/internal-entity-config.cjs +3 -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 +2 -1
- package/internal-mappings-config.d.cts +2 -2
- package/internal-mappings-config.d.mts +2 -2
- package/internal-mappings-config.mjs +2 -2
- package/internal-sync-engine.cjs +1 -1
- package/internal-sync-engine.d.cts +1 -1
- package/internal-sync-engine.d.mts +1 -1
- package/internal-sync-engine.mjs +1 -1
- package/internal-view-compiler.cjs +2 -1
- package/internal-view-compiler.d.cts +2 -2
- package/internal-view-compiler.d.mts +2 -2
- package/internal-view-compiler.mjs +2 -2
- package/internal.cjs +5 -5
- package/internal.d.cts +6 -6
- package/internal.d.mts +6 -6
- package/internal.mjs +5 -5
- package/{mapping-form-43bwOTat.d.cts → mapping-form-DH77BXLv.d.mts} +1 -1
- package/{mapping-form-DUvWUYCo.d.mts → mapping-form-J0aU6O7l.d.cts} +1 -1
- package/{mappings-config-BSSwdHiq.cjs → mappings-config-B0RqFLxu.cjs} +30 -1
- package/{mappings-config-bJQT_gSa.mjs → mappings-config-B1b1HVBg.mjs} +25 -2
- package/mappings-config-D9dsonoi.d.mts +85 -0
- package/mappings-config-RQLGx8ub.d.cts +85 -0
- package/{namespace-probe-BSH_p9TK.d.cts → namespace-probe-C6aHjedO.d.cts} +65 -3
- package/{namespace-probe-Dsp5uNkS.d.mts → namespace-probe-Dj-0Q3Hb.d.mts} +65 -3
- package/package.json +1 -1
- package/{sdk-Bj-ZDf4u.cjs → sdk-D4Wp_uxb.cjs} +754 -44
- package/{sdk-BvRD2jAp.mjs → sdk-DXV8M_Io.mjs} +754 -44
- package/sdk.cjs +4 -2
- package/sdk.d.cts +3 -3
- package/sdk.d.mts +3 -3
- package/sdk.mjs +3 -3
- package/{sync-engine-GNqRB8eS.d.cts → sync-engine-BG2O1an2.d.cts} +2 -2
- package/{sync-engine-ZsusW-q_.d.mts → sync-engine-DgQbrJON.d.mts} +2 -2
- package/{sync-engine-Bg6-SznL.mjs → sync-engine-SuxSzbZ4.mjs} +1 -1
- package/{sync-engine-DEJvKw6E.cjs → sync-engine-abE94g6X.cjs} +1 -1
- package/{view-compiler-DplczSjF.cjs → view-compiler-Blu4OXJ7.cjs} +22 -0
- package/{view-compiler-Cin0neDO.d.mts → view-compiler-Bo7pqXW3.d.mts} +12 -2
- package/{view-compiler-DlTpOfZi.mjs → view-compiler-CQx8TRrW.mjs} +17 -1
- package/{view-compiler-Ll9cbIJz.d.cts → view-compiler-CmEdAMwj.d.cts} +12 -2
- package/vite.d.cts +1 -1
- package/vite.d.mts +1 -1
- package/mappings-config-BpcR8bBo.d.mts +0 -167
- package/mappings-config-xsLMn3I-.d.cts +0 -167
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-DtBsl2MQ.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-D4Wp_uxb.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");
|
|
@@ -478,7 +478,7 @@ async function probeModule(opts) {
|
|
|
478
478
|
}
|
|
479
479
|
//#endregion
|
|
480
480
|
//#region src/workspace-graph.ts
|
|
481
|
-
const WINDOWS_SEP$
|
|
481
|
+
const WINDOWS_SEP$3 = /\\/g;
|
|
482
482
|
const MODULE_EXT$1 = /\.(ts|tsx|js|jsx|mjs|cjs|cts|mts)$/;
|
|
483
483
|
const BRACE_GROUP = /\{([^{}]*)\}/;
|
|
484
484
|
const MAX_GLOB_DEPTH = 8;
|
|
@@ -709,13 +709,13 @@ function exportsAllowSubpath(packageDir, subpath) {
|
|
|
709
709
|
function toPackageSpecifier(pkg, absPath) {
|
|
710
710
|
const target = (0, node_path.resolve)(absPath);
|
|
711
711
|
if (resolvePackageEntry(pkg.dir) === target) return pkg.name;
|
|
712
|
-
const subpath = (0, node_path.relative)(pkg.dir, target).replace(WINDOWS_SEP$
|
|
712
|
+
const subpath = (0, node_path.relative)(pkg.dir, target).replace(WINDOWS_SEP$3, "/").replace(MODULE_EXT$1, "");
|
|
713
713
|
if (subpath.length === 0) return pkg.name;
|
|
714
714
|
return exportsAllowSubpath(pkg.dir, subpath) ? `${pkg.name}/${subpath}` : null;
|
|
715
715
|
}
|
|
716
716
|
//#endregion
|
|
717
717
|
//#region src/discover-schema.ts
|
|
718
|
-
const WINDOWS_SEP$
|
|
718
|
+
const WINDOWS_SEP$2 = /\\/g;
|
|
719
719
|
const MODULE_EXT = /\.(ts|js|mjs|cts|mts)$/;
|
|
720
720
|
/**
|
|
721
721
|
* Turn an absolute module path into the specifier the generated config imports
|
|
@@ -740,7 +740,7 @@ function toImportPath(schemaPath, cwd, graph) {
|
|
|
740
740
|
const specifier = toPackageSpecifier(owner, schemaPath);
|
|
741
741
|
if (specifier) return specifier;
|
|
742
742
|
}
|
|
743
|
-
const rel = (0, node_path.relative)(cwd, schemaPath).replace(WINDOWS_SEP$
|
|
743
|
+
const rel = (0, node_path.relative)(cwd, schemaPath).replace(WINDOWS_SEP$2, "/").replace(MODULE_EXT, "");
|
|
744
744
|
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
745
745
|
}
|
|
746
746
|
function isDirectory(path) {
|
|
@@ -1154,6 +1154,46 @@ async function installPackages(options) {
|
|
|
1154
1154
|
}
|
|
1155
1155
|
}
|
|
1156
1156
|
//#endregion
|
|
1157
|
+
//#region src/install-needed.ts
|
|
1158
|
+
/** Exact `name@version` only. Anything else is left for the package manager. */
|
|
1159
|
+
const EXACT_VERSION = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
|
|
1160
|
+
/**
|
|
1161
|
+
* Split `name@version`, tolerating scoped names where the leading `@` is part
|
|
1162
|
+
* of the name rather than a separator. Returns a null version for a bare name.
|
|
1163
|
+
*/
|
|
1164
|
+
function parseSpec(spec) {
|
|
1165
|
+
const at = spec.lastIndexOf("@");
|
|
1166
|
+
if (at <= 0) return {
|
|
1167
|
+
name: spec,
|
|
1168
|
+
version: null
|
|
1169
|
+
};
|
|
1170
|
+
return {
|
|
1171
|
+
name: spec.slice(0, at),
|
|
1172
|
+
version: spec.slice(at + 1)
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Which of `packages` still need installing.
|
|
1177
|
+
*
|
|
1178
|
+
* Deliberately conservative — a spec is only dropped when it is provably
|
|
1179
|
+
* satisfied. Tarball paths, `file:` specs and semver ranges are always kept:
|
|
1180
|
+
* the first two name no version to compare, and resolving the third needs a
|
|
1181
|
+
* semver implementation the CLI does not ship. Erring toward installing costs
|
|
1182
|
+
* a redundant install; erring the other way silently skips one the developer
|
|
1183
|
+
* asked for.
|
|
1184
|
+
*/
|
|
1185
|
+
function packagesNeedingInstall(packages, installedVersion) {
|
|
1186
|
+
return packages.filter((spec) => {
|
|
1187
|
+
if (spec.startsWith(".") || spec.startsWith("/") || spec.includes(":") || spec.includes("/") && !spec.startsWith("@")) return true;
|
|
1188
|
+
const { name, version } = parseSpec(spec);
|
|
1189
|
+
const present = installedVersion(name);
|
|
1190
|
+
if (present === null) return true;
|
|
1191
|
+
if (version === null) return false;
|
|
1192
|
+
if (!EXACT_VERSION.test(version)) return true;
|
|
1193
|
+
return present !== version;
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
//#endregion
|
|
1157
1197
|
//#region src/is-entry.ts
|
|
1158
1198
|
function isEntrypoint(argv1, moduleUrl, realpath = node_fs.realpathSync) {
|
|
1159
1199
|
if (!argv1) return false;
|
|
@@ -1503,8 +1543,10 @@ function planInit(schema, overrides = {}) {
|
|
|
1503
1543
|
if (built && !contact) todos.push(`"${built.tableName}" was chosen as the contact entity but none of its columns could be mapped to name/email.`);
|
|
1504
1544
|
let company = null;
|
|
1505
1545
|
if (companyTable) {
|
|
1506
|
-
const
|
|
1546
|
+
const candidateTodos = [];
|
|
1547
|
+
const candidate = buildEntity(companyTable, "company", candidateTodos);
|
|
1507
1548
|
if (hasMappedFields(candidate)) {
|
|
1549
|
+
todos.push(...candidateTodos);
|
|
1508
1550
|
company = candidate;
|
|
1509
1551
|
if (require_internal_entity_heuristics.matchesConcept(companyTable.tableName, "contact")) todos.push(`"${companyTable.tableName}" matches both contact and company naming — assigned to company. Swap it to your contact entity if that's wrong.`);
|
|
1510
1552
|
} else todos.push(`"${companyTable.tableName}" looked like a company but no fields could be mapped — left it out. Add it to \`entities\` once you know which columns map to name/domain.`);
|
|
@@ -1613,6 +1655,106 @@ function rankContactCandidates(tables) {
|
|
|
1613
1655
|
}));
|
|
1614
1656
|
}
|
|
1615
1657
|
//#endregion
|
|
1658
|
+
//#region src/config-import.ts
|
|
1659
|
+
const WINDOWS_SEP$1 = /\\/g;
|
|
1660
|
+
function configImport(generatedFilePath) {
|
|
1661
|
+
const rel = (0, node_path.relative)((0, node_path.dirname)(generatedFilePath), "clivly.config").replace(WINDOWS_SEP$1, "/");
|
|
1662
|
+
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
1663
|
+
}
|
|
1664
|
+
//#endregion
|
|
1665
|
+
//#region src/boot-hook.ts
|
|
1666
|
+
const BOOT_HOOK_PATHS = {
|
|
1667
|
+
nextjs: "instrumentation.ts",
|
|
1668
|
+
nuxt: "server/plugins/clivly.ts",
|
|
1669
|
+
sveltekit: "src/hooks.server.ts",
|
|
1670
|
+
"tanstack-start": null,
|
|
1671
|
+
remix: null,
|
|
1672
|
+
unknown: null
|
|
1673
|
+
};
|
|
1674
|
+
function bootHookPath(framework, layout) {
|
|
1675
|
+
const path = BOOT_HOOK_PATHS[framework];
|
|
1676
|
+
if (path === null) return null;
|
|
1677
|
+
if (framework === "nextjs" && layout === "src/app") return `src/${path}`;
|
|
1678
|
+
return path;
|
|
1679
|
+
}
|
|
1680
|
+
const FAILURE_NOTE = "Clivly never blocks your app from starting: a failed check-in is logged and retried on the next tick.";
|
|
1681
|
+
function nextjsHook(importPath) {
|
|
1682
|
+
return `// Clivly boot hook. Next.js calls register() once when the server starts.
|
|
1683
|
+
// ${FAILURE_NOTE}
|
|
1684
|
+
export async function register() {
|
|
1685
|
+
// The keep-alive loop is a setInterval, which does not survive on the edge
|
|
1686
|
+
// runtime — it would stop ticking after the first response and the dashboard
|
|
1687
|
+
// would show an app that checked in once and then went stale forever.
|
|
1688
|
+
if (process.env.NEXT_RUNTIME !== "nodejs") {
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1691
|
+
try {
|
|
1692
|
+
const { default: clivly } = await import("${importPath}");
|
|
1693
|
+
await clivly.start();
|
|
1694
|
+
} catch (error) {
|
|
1695
|
+
console.warn("[clivly] could not check in:", error);
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
`;
|
|
1699
|
+
}
|
|
1700
|
+
function nuxtHook(importPath) {
|
|
1701
|
+
return `import clivly from "${importPath}";
|
|
1702
|
+
|
|
1703
|
+
// Clivly boot hook. Nitro runs this once when the server starts.
|
|
1704
|
+
// ${FAILURE_NOTE}
|
|
1705
|
+
export default defineNitroPlugin(() => {
|
|
1706
|
+
clivly.start().catch((error: unknown) => {
|
|
1707
|
+
console.warn("[clivly] could not check in:", error);
|
|
1708
|
+
});
|
|
1709
|
+
});
|
|
1710
|
+
`;
|
|
1711
|
+
}
|
|
1712
|
+
function sveltekitHook(importPath) {
|
|
1713
|
+
return `import clivly from "${importPath}";
|
|
1714
|
+
|
|
1715
|
+
// Clivly boot hook. This module is evaluated once when the server starts.
|
|
1716
|
+
// ${FAILURE_NOTE}
|
|
1717
|
+
clivly.start().catch((error: unknown) => {
|
|
1718
|
+
console.warn("[clivly] could not check in:", error);
|
|
1719
|
+
});
|
|
1720
|
+
`;
|
|
1721
|
+
}
|
|
1722
|
+
const RENDERERS = {
|
|
1723
|
+
nextjs: nextjsHook,
|
|
1724
|
+
nuxt: nuxtHook,
|
|
1725
|
+
sveltekit: sveltekitHook
|
|
1726
|
+
};
|
|
1727
|
+
function renderBootHook(framework, layout) {
|
|
1728
|
+
const path = bootHookPath(framework, layout);
|
|
1729
|
+
const render = RENDERERS[framework];
|
|
1730
|
+
if (!(path && render)) throw new Error(`No standalone boot hook for framework: ${framework}`);
|
|
1731
|
+
return render(configImport(path));
|
|
1732
|
+
}
|
|
1733
|
+
const NEXT_INSTRUMENTATION_STABLE_MAJOR = 15;
|
|
1734
|
+
const LEADING_MAJOR = /(\d+)/;
|
|
1735
|
+
/** Does this Next.js version range need the experimental flag to run our hook? */
|
|
1736
|
+
function nextNeedsInstrumentationFlag(versionRange) {
|
|
1737
|
+
if (!versionRange) return false;
|
|
1738
|
+
const major = LEADING_MAJOR.exec(versionRange);
|
|
1739
|
+
if (!major?.[1]) return false;
|
|
1740
|
+
return Number(major[1]) < NEXT_INSTRUMENTATION_STABLE_MAJOR;
|
|
1741
|
+
}
|
|
1742
|
+
const NEXT_INSTRUMENTATION_FLAG_STEP = `Your Next.js version reads instrumentation.ts only when it is switched on. Add this to next.config, or Clivly will never be told your app started:
|
|
1743
|
+
experimental: { instrumentationHook: true }`;
|
|
1744
|
+
const REMIX_BOOT_STEP = `Start Clivly when your app boots. Add this near the top of app/entry.server.tsx:
|
|
1745
|
+
import clivly from "./clivly.config";
|
|
1746
|
+
clivly.start().catch((error) => console.warn("[clivly] could not check in:", error));`;
|
|
1747
|
+
/** The boot hook as a scaffold entry, or null when this framework is patched. */
|
|
1748
|
+
function planBootHook(framework, layout) {
|
|
1749
|
+
const path = bootHookPath(framework, layout);
|
|
1750
|
+
if (!path) return null;
|
|
1751
|
+
return {
|
|
1752
|
+
path,
|
|
1753
|
+
description: "Clivly boot hook — starts check-in when your app runs",
|
|
1754
|
+
owned: true
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
//#endregion
|
|
1616
1758
|
//#region src/render-config.ts
|
|
1617
1759
|
const CLIVLY_ENV_KEYS = [
|
|
1618
1760
|
{
|
|
@@ -1923,10 +2065,6 @@ function routePath(framework, kind, layout = "app") {
|
|
|
1923
2065
|
}
|
|
1924
2066
|
const WINDOWS_SEP = /\\/g;
|
|
1925
2067
|
const LEADING_DOT_SLASH = /^\.\//;
|
|
1926
|
-
function configImport(routeFilePath) {
|
|
1927
|
-
const rel = (0, node_path.relative)((0, node_path.dirname)(routeFilePath), "clivly.config").replace(WINDOWS_SEP, "/");
|
|
1928
|
-
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
1929
|
-
}
|
|
1930
2068
|
function hostImportPath(routeFilePath, appRelative, paths) {
|
|
1931
2069
|
const fromRoot = appRelative.replace(LEADING_DOT_SLASH, "");
|
|
1932
2070
|
const alias = aliasFor({
|
|
@@ -2014,6 +2152,7 @@ const COMMON_FILES = [{
|
|
|
2014
2152
|
function planScaffold(framework, layout = "app") {
|
|
2015
2153
|
const paths = ROUTE_PATHS[framework];
|
|
2016
2154
|
if (!paths) return [];
|
|
2155
|
+
const boot = planBootHook(framework, layout);
|
|
2017
2156
|
return [
|
|
2018
2157
|
{
|
|
2019
2158
|
path: applyLayout(framework, paths.tick, layout),
|
|
@@ -2025,6 +2164,7 @@ function planScaffold(framework, layout = "app") {
|
|
|
2025
2164
|
description: "Clivly auth-verify route (createAuthVerifyHandler) — finish resolveUser",
|
|
2026
2165
|
owned: true
|
|
2027
2166
|
},
|
|
2167
|
+
...boot ? [boot] : [],
|
|
2028
2168
|
...COMMON_FILES
|
|
2029
2169
|
];
|
|
2030
2170
|
}
|
|
@@ -2038,6 +2178,45 @@ function planWidgetScaffold(framework, layout = "app") {
|
|
|
2038
2178
|
}];
|
|
2039
2179
|
}
|
|
2040
2180
|
//#endregion
|
|
2181
|
+
//#region src/vite-boot-patch.ts
|
|
2182
|
+
/** Config filenames to look for, in precedence order. */
|
|
2183
|
+
const VITE_CONFIG_CANDIDATES = [
|
|
2184
|
+
"vite.config.ts",
|
|
2185
|
+
"vite.config.mts",
|
|
2186
|
+
"vite.config.js",
|
|
2187
|
+
"vite.config.mjs"
|
|
2188
|
+
];
|
|
2189
|
+
const PLUGIN_IMPORT = "import { clivlyVite } from \"clivly/vite\";";
|
|
2190
|
+
const CONFIG_IMPORT = "import clivly from \"./clivly.config\";";
|
|
2191
|
+
const PLUGIN_CALL = "clivlyVite(clivly)";
|
|
2192
|
+
const PLUGINS_ARRAY = /(^|[\s{,])plugins\s*:\s*\[/g;
|
|
2193
|
+
/** The last import line's end offset, so new imports land after the existing ones. */
|
|
2194
|
+
const IMPORT_LINE = /^\s*import\s.+?from\s+["'].+?["'];?\s*$/gm;
|
|
2195
|
+
function insertImports(source) {
|
|
2196
|
+
const last = [...source.matchAll(IMPORT_LINE)].at(-1);
|
|
2197
|
+
const block = `${PLUGIN_IMPORT}\n${CONFIG_IMPORT}`;
|
|
2198
|
+
if (!last) return `${block}\n\n${source}`;
|
|
2199
|
+
const at = (last.index ?? 0) + last[0].length;
|
|
2200
|
+
return `${source.slice(0, at)}\n${block}${source.slice(at)}`;
|
|
2201
|
+
}
|
|
2202
|
+
/**
|
|
2203
|
+
* Add the Clivly Vite plugin to an existing config.
|
|
2204
|
+
*
|
|
2205
|
+
* Returns the patched source, or `null` when the config is already patched or
|
|
2206
|
+
* is not a shape we can edit safely.
|
|
2207
|
+
*/
|
|
2208
|
+
function patchViteConfig(source) {
|
|
2209
|
+
if (source.includes(PLUGIN_CALL)) return null;
|
|
2210
|
+
const matches = [...source.matchAll(PLUGINS_ARRAY)];
|
|
2211
|
+
if (matches.length !== 1) return null;
|
|
2212
|
+
const match = matches[0];
|
|
2213
|
+
if (!match) return null;
|
|
2214
|
+
const openBracketEnd = (match.index ?? 0) + match[0].length;
|
|
2215
|
+
const rest = source.slice(openBracketEnd);
|
|
2216
|
+
const separator = rest.startsWith("\n") ? "" : " ";
|
|
2217
|
+
return insertImports(`${source.slice(0, openBracketEnd)}${PLUGIN_CALL},${separator}${rest}`);
|
|
2218
|
+
}
|
|
2219
|
+
//#endregion
|
|
2041
2220
|
//#region src/plan-mutations.ts
|
|
2042
2221
|
function fileAction(existing, next, force) {
|
|
2043
2222
|
if (existing === null) return "create";
|
|
@@ -2049,8 +2228,10 @@ function hasKey(content, key) {
|
|
|
2049
2228
|
}
|
|
2050
2229
|
function planFiles(args) {
|
|
2051
2230
|
const mutations = [];
|
|
2231
|
+
const bootPath = bootHookPath(args.framework, args.layout);
|
|
2052
2232
|
const render = (path) => {
|
|
2053
2233
|
if (path === "clivly.config.ts") return renderInitConfig(args.plan);
|
|
2234
|
+
if (bootPath !== null && path === bootPath) return renderBootHook(args.framework, args.layout);
|
|
2054
2235
|
const kind = path.includes("verify") ? "verify" : "tick";
|
|
2055
2236
|
return renderRouteFile(args.framework, kind, args.authWiring, args.layout, parseTsconfig(args.readFile((0, node_path.join)(args.cwd, "tsconfig.json")) ?? "")?.paths);
|
|
2056
2237
|
};
|
|
@@ -2078,10 +2259,67 @@ function planEnv(args) {
|
|
|
2078
2259
|
action: envAction(addedKeys.length, existing.length)
|
|
2079
2260
|
};
|
|
2080
2261
|
}
|
|
2262
|
+
function declaredNextVersion(args) {
|
|
2263
|
+
const raw = args.readFile((0, node_path.join)(args.cwd, "package.json"));
|
|
2264
|
+
if (!raw) return;
|
|
2265
|
+
let pkg = {};
|
|
2266
|
+
try {
|
|
2267
|
+
pkg = JSON.parse(raw);
|
|
2268
|
+
} catch {
|
|
2269
|
+
pkg = {};
|
|
2270
|
+
}
|
|
2271
|
+
return pkg.dependencies?.next ?? pkg.devDependencies?.next;
|
|
2272
|
+
}
|
|
2273
|
+
function planBootPatch(args) {
|
|
2274
|
+
if (bootHookPath(args.framework, args.layout) !== null) return {
|
|
2275
|
+
file: null,
|
|
2276
|
+
manual: args.framework === "nextjs" && nextNeedsInstrumentationFlag(declaredNextVersion(args)) ? NEXT_INSTRUMENTATION_FLAG_STEP : null
|
|
2277
|
+
};
|
|
2278
|
+
if (args.framework === "remix") return {
|
|
2279
|
+
file: null,
|
|
2280
|
+
manual: REMIX_BOOT_STEP
|
|
2281
|
+
};
|
|
2282
|
+
if (args.framework !== "tanstack-start") return {
|
|
2283
|
+
file: null,
|
|
2284
|
+
manual: null
|
|
2285
|
+
};
|
|
2286
|
+
for (const candidate of VITE_CONFIG_CANDIDATES) {
|
|
2287
|
+
const existing = args.readFile((0, node_path.join)(args.cwd, candidate));
|
|
2288
|
+
if (existing === null) continue;
|
|
2289
|
+
const patched = patchViteConfig(existing);
|
|
2290
|
+
if (patched === null) return existing.includes("clivlyVite(clivly)") ? {
|
|
2291
|
+
file: {
|
|
2292
|
+
path: candidate,
|
|
2293
|
+
contents: existing,
|
|
2294
|
+
action: "unchanged"
|
|
2295
|
+
},
|
|
2296
|
+
manual: null
|
|
2297
|
+
} : {
|
|
2298
|
+
file: null,
|
|
2299
|
+
manual: `Add the Clivly plugin to ${candidate} so your app checks in when it runs:\n import { clivlyVite } from "clivly/vite";\n import clivly from "./clivly.config";\n // then, inside defineConfig: plugins: [clivlyVite(clivly), …]`
|
|
2300
|
+
};
|
|
2301
|
+
return {
|
|
2302
|
+
file: {
|
|
2303
|
+
path: candidate,
|
|
2304
|
+
contents: patched,
|
|
2305
|
+
action: "patch"
|
|
2306
|
+
},
|
|
2307
|
+
manual: null
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2310
|
+
return {
|
|
2311
|
+
file: null,
|
|
2312
|
+
manual: null
|
|
2313
|
+
};
|
|
2314
|
+
}
|
|
2081
2315
|
function planMutations(args) {
|
|
2316
|
+
const files = planFiles(args);
|
|
2317
|
+
const boot = planBootPatch(args);
|
|
2318
|
+
if (boot.file) files.push(boot.file);
|
|
2082
2319
|
return {
|
|
2083
|
-
files
|
|
2084
|
-
env: planEnv(args)
|
|
2320
|
+
files,
|
|
2321
|
+
env: planEnv(args),
|
|
2322
|
+
manualSteps: boot.manual ? [boot.manual] : []
|
|
2085
2323
|
};
|
|
2086
2324
|
}
|
|
2087
2325
|
function planWidgetMutations(args) {
|
|
@@ -2095,6 +2333,49 @@ function planWidgetMutations(args) {
|
|
|
2095
2333
|
});
|
|
2096
2334
|
}
|
|
2097
2335
|
//#endregion
|
|
2336
|
+
//#region src/quiet-host-output.ts
|
|
2337
|
+
/**
|
|
2338
|
+
* Run `fn` with the process's stdout/stderr captured. Always restores the
|
|
2339
|
+
* original writers, including when `fn` throws — a leaked patch would silence
|
|
2340
|
+
* the whole CLI for the rest of the run.
|
|
2341
|
+
*/
|
|
2342
|
+
async function captureHostOutput(fn) {
|
|
2343
|
+
const originals = {
|
|
2344
|
+
stdout: process.stdout.write,
|
|
2345
|
+
stderr: process.stderr.write
|
|
2346
|
+
};
|
|
2347
|
+
let output = "";
|
|
2348
|
+
const capture = (chunk, encoding, callback) => {
|
|
2349
|
+
output += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
|
|
2350
|
+
const done = typeof encoding === "function" ? encoding : callback;
|
|
2351
|
+
if (typeof done === "function") done();
|
|
2352
|
+
return true;
|
|
2353
|
+
};
|
|
2354
|
+
process.stdout.write = capture;
|
|
2355
|
+
process.stderr.write = capture;
|
|
2356
|
+
try {
|
|
2357
|
+
return {
|
|
2358
|
+
result: await fn(),
|
|
2359
|
+
output
|
|
2360
|
+
};
|
|
2361
|
+
} catch (error) {
|
|
2362
|
+
if (output.trim().length > 0 && error instanceof Error) error.message = `${error.message}\n${output.trim()}`;
|
|
2363
|
+
throw error;
|
|
2364
|
+
} finally {
|
|
2365
|
+
process.stdout.write = originals.stdout;
|
|
2366
|
+
process.stderr.write = originals.stderr;
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
/**
|
|
2370
|
+
* Run `fn` quietly, discarding whatever the host printed on success. On failure
|
|
2371
|
+
* the captured output is attached to the error's message, so the host's own
|
|
2372
|
+
* explanation is not lost.
|
|
2373
|
+
*/
|
|
2374
|
+
async function quietly(fn) {
|
|
2375
|
+
const { result } = await captureHostOutput(fn);
|
|
2376
|
+
return result;
|
|
2377
|
+
}
|
|
2378
|
+
//#endregion
|
|
2098
2379
|
//#region src/probe-auth.ts
|
|
2099
2380
|
const AUTH_MODULE_PATHS = [
|
|
2100
2381
|
"auth",
|
|
@@ -2117,15 +2398,32 @@ function defaultLoader(cwd) {
|
|
|
2117
2398
|
return (specifier) => jiti$1.import(specifier);
|
|
2118
2399
|
}
|
|
2119
2400
|
function probeAuthInstance(cwd, load) {
|
|
2120
|
-
return probeModule({
|
|
2401
|
+
return quietly(() => probeModule({
|
|
2121
2402
|
candidates: authInstanceCandidates(cwd),
|
|
2122
2403
|
cwd,
|
|
2123
2404
|
load: load ?? defaultLoader(cwd),
|
|
2124
2405
|
match: isBetterAuthInstance
|
|
2125
|
-
});
|
|
2406
|
+
}));
|
|
2126
2407
|
}
|
|
2127
2408
|
//#endregion
|
|
2128
2409
|
//#region src/push-schema.ts
|
|
2410
|
+
/**
|
|
2411
|
+
* "1 mapping" / "3 mappings" — a count nobody has to decode. The plural is
|
|
2412
|
+
* explicit because appending "s" turns "entity" into "entitys".
|
|
2413
|
+
*/
|
|
2414
|
+
function countOf(n, singular, plural = `${singular}s`) {
|
|
2415
|
+
return `${n} ${n === 1 ? singular : plural}`;
|
|
2416
|
+
}
|
|
2417
|
+
function reportSchema(report, out) {
|
|
2418
|
+
const createdObjects = report.objectTypes.filter((o) => o.created);
|
|
2419
|
+
if (createdObjects.length > 0) out(` ✅ Created ${countOf(createdObjects.length, "custom object")}: ${createdObjects.map((o) => o.slug).join(", ")}.\n`);
|
|
2420
|
+
else if (report.objectTypes.length > 0) out(` ✅ ${countOf(report.objectTypes.length, "custom object")} already present.\n`);
|
|
2421
|
+
const created = report.mappings.filter((m) => m.created);
|
|
2422
|
+
const updated = report.mappings.filter((m) => m.updated);
|
|
2423
|
+
if (created.length > 0) out(` ✅ Mapped ${countOf(created.length, "entity", "entities")}: ${created.map((m) => m.entityKey).join(", ")}.\n`);
|
|
2424
|
+
if (updated.length > 0) out(` ✅ Updated ${countOf(updated.length, "mapping")}: ${updated.map((m) => m.entityKey).join(", ")}.\n`);
|
|
2425
|
+
if (report.mappings.length > 0 && created.length === 0 && updated.length === 0) out(` ✅ ${countOf(report.mappings.length, "mapping")} already match your config.\n`);
|
|
2426
|
+
}
|
|
2129
2427
|
async function runPushSchema(options) {
|
|
2130
2428
|
const out = options.out ?? ((line) => process.stdout.write(line));
|
|
2131
2429
|
const path = resolveConfigPath(options.cwd, options.configPath);
|
|
@@ -2153,6 +2451,29 @@ async function runPushSchema(options) {
|
|
|
2153
2451
|
}
|
|
2154
2452
|
out(" ✅ Schema reported to Clivly.\n");
|
|
2155
2453
|
out(" Integration liveness and trigger URL were refreshed too.\n");
|
|
2454
|
+
if (sdk.ensureSchema) {
|
|
2455
|
+
let report;
|
|
2456
|
+
try {
|
|
2457
|
+
report = await sdk.ensureSchema();
|
|
2458
|
+
} catch (error) {
|
|
2459
|
+
out(` ❌ Clivly refused your schema — ${error.message}\n`);
|
|
2460
|
+
return 1;
|
|
2461
|
+
}
|
|
2462
|
+
if (report === null) {
|
|
2463
|
+
out(" ⚠️ Could not declare the objects and mappings in your config.\n");
|
|
2464
|
+
out(" Run `clivly status`, then `clivly push-schema` again.\n");
|
|
2465
|
+
} else reportSchema(report, out);
|
|
2466
|
+
out(" Open Integrations → Mapping to review them.\n");
|
|
2467
|
+
return 0;
|
|
2468
|
+
}
|
|
2469
|
+
const ensured = await sdk.ensureCustomObjects?.();
|
|
2470
|
+
if (ensured === null) {
|
|
2471
|
+
out(" ⚠️ Could not create the custom objects your config declares.\n");
|
|
2472
|
+
out(" Run `clivly status`, then `clivly push-schema` again.\n");
|
|
2473
|
+
} else if (ensured && ensured.length > 0) {
|
|
2474
|
+
const created = ensured.filter((o) => o.created);
|
|
2475
|
+
out(created.length > 0 ? ` ✅ Created ${created.length} custom object(s): ${created.map((o) => o.slug).join(", ")}.\n` : ` ✅ ${ensured.length} custom object(s) already present.\n`);
|
|
2476
|
+
}
|
|
2156
2477
|
out(" Open Integrations → Mapping to map your tables.\n");
|
|
2157
2478
|
return 0;
|
|
2158
2479
|
}
|
|
@@ -2733,8 +3054,19 @@ const defaultConnect = (apiKey, apiUrl) => require_sdk.createClivlySDK({
|
|
|
2733
3054
|
apiUrl,
|
|
2734
3055
|
retry: { maxAttempts: 1 }
|
|
2735
3056
|
}).ping();
|
|
3057
|
+
/**
|
|
3058
|
+
* Exported for tests only — the rule it encodes is the point.
|
|
3059
|
+
*
|
|
3060
|
+
* A skipped check was never evaluated (nothing to check yet), which is not the
|
|
3061
|
+
* same as a warning: a warning is a working setup carrying real risk. Marking
|
|
3062
|
+
* "not applicable" with ⚠️ put a warning on every fresh install for something
|
|
3063
|
+
* the developer had not done wrong, which is exactly how people are trained to
|
|
3064
|
+
* stop reading warnings — the same reasoning `status-stages.ts` gives for
|
|
3065
|
+
* refusing to turn a stage red on a setup that works.
|
|
3066
|
+
*/
|
|
2736
3067
|
function subMark(sub) {
|
|
2737
|
-
if (sub.skipped
|
|
3068
|
+
if (sub.skipped) return "⏭️ ";
|
|
3069
|
+
if (sub.warn) return "⚠️ ";
|
|
2738
3070
|
return sub.ok ? "✅" : "❌";
|
|
2739
3071
|
}
|
|
2740
3072
|
function renderStage(result, verbose, out) {
|
|
@@ -2897,6 +3229,14 @@ function runAdd(argv, cwd, deps = defaultAddDeps) {
|
|
|
2897
3229
|
}
|
|
2898
3230
|
return 0;
|
|
2899
3231
|
}
|
|
3232
|
+
function readInstalledVersion(cwd, name) {
|
|
3233
|
+
try {
|
|
3234
|
+
const manifest = (0, node_path.join)(cwd, "node_modules", ...name.split("/"), "package.json");
|
|
3235
|
+
return JSON.parse((0, node_fs.readFileSync)(manifest, "utf8")).version ?? null;
|
|
3236
|
+
} catch {
|
|
3237
|
+
return null;
|
|
3238
|
+
}
|
|
3239
|
+
}
|
|
2900
3240
|
function readOwnVersion() {
|
|
2901
3241
|
const req = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
2902
3242
|
for (const rel of ["./package.json", "../package.json"]) try {
|
|
@@ -2994,6 +3334,7 @@ function resolveInitDeps(options) {
|
|
|
2994
3334
|
install: options.installPackages ?? installPackages,
|
|
2995
3335
|
probeAuth: options.probeAuthInstance ?? probeAuthInstance,
|
|
2996
3336
|
readEnv: options.readEnv ?? defaultReadEnv,
|
|
3337
|
+
readInstalledVersion: options.readInstalledVersion ?? readInstalledVersion,
|
|
2997
3338
|
readOwnVersion: options.readOwnVersion ?? readOwnVersion,
|
|
2998
3339
|
writeEnv: options.writeEnv ?? defaultWriteEnv,
|
|
2999
3340
|
isInteractive: options.yes ? () => false : options.isInteractive ?? defaultIsInteractive,
|
|
@@ -3023,8 +3364,8 @@ async function acquireSchema(options, deps) {
|
|
|
3023
3364
|
deps.framework = framework;
|
|
3024
3365
|
const layout = deps.resolveAppLayout(options.cwd, deps.fileExists);
|
|
3025
3366
|
deps.layout = layout;
|
|
3026
|
-
deps.authWiring = renderAuthWiring(auth, auth === "better-auth" ? authInstanceFromFlag(options.authImportFlag) ?? await deps.probeAuth(options.cwd) : null);
|
|
3027
3367
|
out("\nClivly init\n");
|
|
3368
|
+
deps.authWiring = renderAuthWiring(auth, auth === "better-auth" ? authInstanceFromFlag(options.authImportFlag) ?? await deps.probeAuth(options.cwd) : null);
|
|
3028
3369
|
out(` Framework: ${FRAMEWORK_LABELS[framework]}\n`);
|
|
3029
3370
|
out(` ORM: ${orm}\n`);
|
|
3030
3371
|
out(` Auth: ${auth}${deps.authWiring.complete ? " (wired)" : ""}\n`);
|
|
@@ -3093,12 +3434,12 @@ function applyMutations(options, deps, mutations) {
|
|
|
3093
3434
|
deps.writeConfig((0, node_path.join)(options.cwd, file.path), file.contents);
|
|
3094
3435
|
deps.out(` ✓ ${file.path}\n`);
|
|
3095
3436
|
}
|
|
3096
|
-
if (mutations.env.action === "skip")
|
|
3097
|
-
|
|
3098
|
-
|
|
3437
|
+
if (mutations.env.action === "skip") deps.out(" • .env already has every CLIVLY_* key — skipped\n");
|
|
3438
|
+
else {
|
|
3439
|
+
deps.writeEnv(options.cwd, mutations.env.contents);
|
|
3440
|
+
deps.out(" ✓ .env placeholders\n");
|
|
3099
3441
|
}
|
|
3100
|
-
|
|
3101
|
-
deps.out(" ✓ .env placeholders\n");
|
|
3442
|
+
for (const step of mutations.manualSteps) deps.out(`\n ! ${step}\n`);
|
|
3102
3443
|
}
|
|
3103
3444
|
const DIFF_CONTEXT_LIMIT = 40;
|
|
3104
3445
|
function renderDiff(existing, next) {
|
|
@@ -3118,6 +3459,7 @@ function previewMutations(deps, mutations, options) {
|
|
|
3118
3459
|
conflict: " (differs — --diff to see, --force to overwrite)",
|
|
3119
3460
|
create: ` (${lines} lines)`,
|
|
3120
3461
|
overwrite: ` (${lines} lines)`,
|
|
3462
|
+
patch: " (your file, plus the Clivly plugin)",
|
|
3121
3463
|
unchanged: " (already up to date)"
|
|
3122
3464
|
}[file.action];
|
|
3123
3465
|
out(` ${file.action.padEnd(9)} ${file.path}${suffix}\n`);
|
|
@@ -3175,13 +3517,17 @@ async function scaffold(options, deps, plan) {
|
|
|
3175
3517
|
});
|
|
3176
3518
|
return 0;
|
|
3177
3519
|
}
|
|
3178
|
-
const
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3520
|
+
const missing = packagesNeedingInstall(packages, (name) => deps.readInstalledVersion(options.cwd, name));
|
|
3521
|
+
if (missing.length === 0) out(`Already installed (${pm}) — nothing to fetch\n`);
|
|
3522
|
+
else {
|
|
3523
|
+
const result = await deps.install({
|
|
3524
|
+
cwd: options.cwd,
|
|
3525
|
+
pm,
|
|
3526
|
+
packages: missing
|
|
3527
|
+
});
|
|
3528
|
+
if (result.ok) out(`Installed clivly (${pm})\n`);
|
|
3529
|
+
else out(`Could not install automatically — run:\n ${result.command}\n`);
|
|
3530
|
+
}
|
|
3185
3531
|
applyMutations(options, deps, mutations);
|
|
3186
3532
|
let loginRan = false;
|
|
3187
3533
|
if (credentialsMissing(mutations.env.contents)) {
|
package/index.d.cts
CHANGED
|
@@ -98,6 +98,7 @@ interface RunInitOptions {
|
|
|
98
98
|
readDeps?: (cwd: string) => Record<string, string>;
|
|
99
99
|
readEnv?: (cwd: string) => string;
|
|
100
100
|
readFile?: (path: string) => string | null;
|
|
101
|
+
readInstalledVersion?: (cwd: string, name: string) => string | null;
|
|
101
102
|
readOwnVersion?: () => string | undefined;
|
|
102
103
|
resolveAppLayout?: (cwd: string, fileExists: (path: string) => boolean) => AppLayout;
|
|
103
104
|
resolveSchemaPath?: (cwd: string) => string | null;
|
package/index.d.mts
CHANGED
|
@@ -98,6 +98,7 @@ interface RunInitOptions {
|
|
|
98
98
|
readDeps?: (cwd: string) => Record<string, string>;
|
|
99
99
|
readEnv?: (cwd: string) => string;
|
|
100
100
|
readFile?: (path: string) => string | null;
|
|
101
|
+
readInstalledVersion?: (cwd: string, name: string) => string | null;
|
|
101
102
|
readOwnVersion?: () => string | undefined;
|
|
102
103
|
resolveAppLayout?: (cwd: string, fileExists: (path: string) => boolean) => AppLayout;
|
|
103
104
|
resolveSchemaPath?: (cwd: string) => string | null;
|