clivly 0.9.1 → 0.9.3
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 +79 -13
- package/core-drizzle.d.cts +1 -1
- package/core-drizzle.d.mts +1 -1
- package/core-entity-config.cjs +2 -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.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 +3 -3
- package/core.d.cts +8 -7
- package/core.d.mts +8 -7
- package/core.mjs +3 -3
- package/{drizzle-DooDUGqO.d.mts → drizzle-CiK8MMEZ.d.mts} +1 -1
- package/{drizzle-jz0lrAgl.d.cts → drizzle-DEtRrqtc.d.cts} +1 -1
- package/drizzle.d.cts +2 -2
- package/drizzle.d.mts +2 -2
- package/{entity-config-fKQhimtD.d.mts → entity-config-B2RA53nw.d.cts} +25 -1
- package/{entity-config-fKQhimtD.d.cts → entity-config-B2RA53nw.d.mts} +25 -1
- package/{entity-config-nuXge6rY.cjs → entity-config-DtBsl2MQ.cjs} +36 -12
- package/{entity-config-D08zAeFY.mjs → entity-config-kUHri9yA.mjs} +31 -13
- package/index.cjs +452 -38
- package/index.d.cts +3 -1
- package/index.d.mts +3 -1
- package/index.mjs +452 -39
- package/integration-state-BK-riZnm.d.cts +130 -0
- package/integration-state-BK-riZnm.d.mts +130 -0
- package/{internal-C-YBW2DB.d.cts → internal-CDY8g9Ic.d.mts} +1 -1
- package/{internal-D4HDi2GC.cjs → internal-CwnEG6Wl.cjs} +1 -1
- package/{internal-BLCK81kB.d.mts → internal-Da_dlcQ_.d.cts} +1 -1
- package/{internal-BGVUG312.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 +2 -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.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 +3 -3
- package/internal.d.cts +8 -7
- package/internal.d.mts +8 -7
- package/internal.mjs +3 -3
- package/{mapping-form-Du36VZr1.d.mts → mapping-form-DH77BXLv.d.mts} +1 -1
- package/{mapping-form-DhY0iFmN.d.cts → mapping-form-J0aU6O7l.d.cts} +1 -1
- package/{mappings-config-C8ynKmDK.cjs → mappings-config-B0RqFLxu.cjs} +30 -1
- package/{mappings-config-DNaYGyKN.mjs → mappings-config-B1b1HVBg.mjs} +25 -2
- package/{mappings-config-B1qNM8VL.d.cts → mappings-config-D9dsonoi.d.mts} +40 -2
- package/{mappings-config-BChZsany.d.mts → mappings-config-RQLGx8ub.d.cts} +40 -2
- package/{namespace-probe-BkUs8raG.d.mts → namespace-probe-BmdGnYMn.d.mts} +72 -3
- package/{namespace-probe-aRFmpU85.d.cts → namespace-probe-EYyxWZnf.d.cts} +72 -3
- package/package.json +1 -1
- package/{sdk-Bj-ZDf4u.cjs → sdk-COLUHhJq.cjs} +755 -44
- package/{sdk-BvRD2jAp.mjs → sdk-CZNXBsgA.mjs} +755 -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-DHQ7rXZe.d.mts → sync-engine-BG2O1an2.d.cts} +2 -2
- package/{sync-engine-DiPjH53O.d.cts → sync-engine-DgQbrJON.d.mts} +2 -2
- package/{view-compiler-CcUwzZut.d.mts → view-compiler-Bo7pqXW3.d.mts} +1 -1
- package/{view-compiler-DqAdVxLq.d.cts → view-compiler-CmEdAMwj.d.cts} +1 -1
- package/vite.d.cts +1 -1
- package/vite.d.mts +1 -1
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-kUHri9yA.mjs";
|
|
3
3
|
import "./mapping-score-C68uAqGB.mjs";
|
|
4
4
|
import { matchesConcept, requiredFieldsFor, scoreFieldMap, scoreRelationships, suggestCursorField, toFieldMap } from "./internal-entity-heuristics.mjs";
|
|
5
|
-
import { t as createClivlySDK } from "./sdk-
|
|
5
|
+
import { t as createClivlySDK } from "./sdk-CZNXBsgA.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";
|
|
@@ -477,7 +477,7 @@ async function probeModule(opts) {
|
|
|
477
477
|
}
|
|
478
478
|
//#endregion
|
|
479
479
|
//#region src/workspace-graph.ts
|
|
480
|
-
const WINDOWS_SEP$
|
|
480
|
+
const WINDOWS_SEP$3 = /\\/g;
|
|
481
481
|
const MODULE_EXT$1 = /\.(ts|tsx|js|jsx|mjs|cjs|cts|mts)$/;
|
|
482
482
|
const BRACE_GROUP = /\{([^{}]*)\}/;
|
|
483
483
|
const MAX_GLOB_DEPTH = 8;
|
|
@@ -708,13 +708,13 @@ function exportsAllowSubpath(packageDir, subpath) {
|
|
|
708
708
|
function toPackageSpecifier(pkg, absPath) {
|
|
709
709
|
const target = resolve(absPath);
|
|
710
710
|
if (resolvePackageEntry(pkg.dir) === target) return pkg.name;
|
|
711
|
-
const subpath = relative(pkg.dir, target).replace(WINDOWS_SEP$
|
|
711
|
+
const subpath = relative(pkg.dir, target).replace(WINDOWS_SEP$3, "/").replace(MODULE_EXT$1, "");
|
|
712
712
|
if (subpath.length === 0) return pkg.name;
|
|
713
713
|
return exportsAllowSubpath(pkg.dir, subpath) ? `${pkg.name}/${subpath}` : null;
|
|
714
714
|
}
|
|
715
715
|
//#endregion
|
|
716
716
|
//#region src/discover-schema.ts
|
|
717
|
-
const WINDOWS_SEP$
|
|
717
|
+
const WINDOWS_SEP$2 = /\\/g;
|
|
718
718
|
const MODULE_EXT = /\.(ts|js|mjs|cts|mts)$/;
|
|
719
719
|
/**
|
|
720
720
|
* Turn an absolute module path into the specifier the generated config imports
|
|
@@ -739,7 +739,7 @@ function toImportPath(schemaPath, cwd, graph) {
|
|
|
739
739
|
const specifier = toPackageSpecifier(owner, schemaPath);
|
|
740
740
|
if (specifier) return specifier;
|
|
741
741
|
}
|
|
742
|
-
const rel = relative(cwd, schemaPath).replace(WINDOWS_SEP$
|
|
742
|
+
const rel = relative(cwd, schemaPath).replace(WINDOWS_SEP$2, "/").replace(MODULE_EXT, "");
|
|
743
743
|
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
744
744
|
}
|
|
745
745
|
function isDirectory(path) {
|
|
@@ -1001,7 +1001,7 @@ Usage:
|
|
|
1001
1001
|
clivly init [options] Scaffold Clivly (config, routes, env file) for your app
|
|
1002
1002
|
clivly add widget [options] Scaffold the chat-session route
|
|
1003
1003
|
clivly status [options] Check your setup, step by step
|
|
1004
|
-
clivly push-schema [options]
|
|
1004
|
+
clivly push-schema [options] Push schema, custom objects and mappings to Clivly
|
|
1005
1005
|
clivly dev [options] Tunnel localhost so remote sync can reach you
|
|
1006
1006
|
clivly login [options] Pair this machine and write credentials to your env file
|
|
1007
1007
|
clivly --version Print the installed clivly version
|
|
@@ -1057,7 +1057,9 @@ Options:
|
|
|
1057
1057
|
Usage:
|
|
1058
1058
|
clivly status [--verbose] [--config <path>] [--env-file <path>]
|
|
1059
1059
|
|
|
1060
|
-
|
|
1060
|
+
Prints two sections. "Workspace" is how far setup has got on Clivly's side,
|
|
1061
|
+
computed by the server so the terminal and the dashboard agree. "This project"
|
|
1062
|
+
runs the local ladder: credential, reachability, config load, edge-safety,
|
|
1061
1063
|
setup checks, sync trigger. Read-only — writes nothing.
|
|
1062
1064
|
|
|
1063
1065
|
Options:
|
|
@@ -1065,13 +1067,23 @@ Options:
|
|
|
1065
1067
|
--config <path> Path to clivly.config.ts
|
|
1066
1068
|
--env-file <path> Extra env file to load
|
|
1067
1069
|
`,
|
|
1068
|
-
"push-schema": `clivly push-schema —
|
|
1070
|
+
"push-schema": `clivly push-schema — push everything your config declares
|
|
1069
1071
|
|
|
1070
1072
|
Usage:
|
|
1071
1073
|
clivly push-schema [--config <path>]
|
|
1072
1074
|
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
+
Pushes three things to Clivly, without deploying your app:
|
|
1076
|
+
1. Your table and column NAMES, so the dashboard can offer them for mapping.
|
|
1077
|
+
Never row data — discovery is names only.
|
|
1078
|
+
2. The custom object types you declared with \`objectType\`.
|
|
1079
|
+
3. The mappings that make those objects actually receive rows.
|
|
1080
|
+
|
|
1081
|
+
Reads your config; writes nothing locally. Safe to re-run — it reports what it
|
|
1082
|
+
created, updated, or left alone.
|
|
1083
|
+
|
|
1084
|
+
Exits 1 if Clivly refuses your config, most often because an entity would
|
|
1085
|
+
overwrite a mapping edited in the dashboard. That makes it usable in CI as a
|
|
1086
|
+
schema gate.
|
|
1075
1087
|
`,
|
|
1076
1088
|
dev: `clivly dev — tunnel localhost so remote sync can reach you
|
|
1077
1089
|
|
|
@@ -1153,6 +1165,46 @@ async function installPackages(options) {
|
|
|
1153
1165
|
}
|
|
1154
1166
|
}
|
|
1155
1167
|
//#endregion
|
|
1168
|
+
//#region src/install-needed.ts
|
|
1169
|
+
/** Exact `name@version` only. Anything else is left for the package manager. */
|
|
1170
|
+
const EXACT_VERSION = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
|
|
1171
|
+
/**
|
|
1172
|
+
* Split `name@version`, tolerating scoped names where the leading `@` is part
|
|
1173
|
+
* of the name rather than a separator. Returns a null version for a bare name.
|
|
1174
|
+
*/
|
|
1175
|
+
function parseSpec(spec) {
|
|
1176
|
+
const at = spec.lastIndexOf("@");
|
|
1177
|
+
if (at <= 0) return {
|
|
1178
|
+
name: spec,
|
|
1179
|
+
version: null
|
|
1180
|
+
};
|
|
1181
|
+
return {
|
|
1182
|
+
name: spec.slice(0, at),
|
|
1183
|
+
version: spec.slice(at + 1)
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Which of `packages` still need installing.
|
|
1188
|
+
*
|
|
1189
|
+
* Deliberately conservative — a spec is only dropped when it is provably
|
|
1190
|
+
* satisfied. Tarball paths, `file:` specs and semver ranges are always kept:
|
|
1191
|
+
* the first two name no version to compare, and resolving the third needs a
|
|
1192
|
+
* semver implementation the CLI does not ship. Erring toward installing costs
|
|
1193
|
+
* a redundant install; erring the other way silently skips one the developer
|
|
1194
|
+
* asked for.
|
|
1195
|
+
*/
|
|
1196
|
+
function packagesNeedingInstall(packages, installedVersion) {
|
|
1197
|
+
return packages.filter((spec) => {
|
|
1198
|
+
if (spec.startsWith(".") || spec.startsWith("/") || spec.includes(":") || spec.includes("/") && !spec.startsWith("@")) return true;
|
|
1199
|
+
const { name, version } = parseSpec(spec);
|
|
1200
|
+
const present = installedVersion(name);
|
|
1201
|
+
if (present === null) return true;
|
|
1202
|
+
if (version === null) return false;
|
|
1203
|
+
if (!EXACT_VERSION.test(version)) return true;
|
|
1204
|
+
return present !== version;
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
1207
|
+
//#endregion
|
|
1156
1208
|
//#region src/is-entry.ts
|
|
1157
1209
|
function isEntrypoint(argv1, moduleUrl, realpath = realpathSync) {
|
|
1158
1210
|
if (!argv1) return false;
|
|
@@ -1614,6 +1666,106 @@ function rankContactCandidates(tables) {
|
|
|
1614
1666
|
}));
|
|
1615
1667
|
}
|
|
1616
1668
|
//#endregion
|
|
1669
|
+
//#region src/config-import.ts
|
|
1670
|
+
const WINDOWS_SEP$1 = /\\/g;
|
|
1671
|
+
function configImport(generatedFilePath) {
|
|
1672
|
+
const rel = relative(dirname(generatedFilePath), "clivly.config").replace(WINDOWS_SEP$1, "/");
|
|
1673
|
+
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
1674
|
+
}
|
|
1675
|
+
//#endregion
|
|
1676
|
+
//#region src/boot-hook.ts
|
|
1677
|
+
const BOOT_HOOK_PATHS = {
|
|
1678
|
+
nextjs: "instrumentation.ts",
|
|
1679
|
+
nuxt: "server/plugins/clivly.ts",
|
|
1680
|
+
sveltekit: "src/hooks.server.ts",
|
|
1681
|
+
"tanstack-start": null,
|
|
1682
|
+
remix: null,
|
|
1683
|
+
unknown: null
|
|
1684
|
+
};
|
|
1685
|
+
function bootHookPath(framework, layout) {
|
|
1686
|
+
const path = BOOT_HOOK_PATHS[framework];
|
|
1687
|
+
if (path === null) return null;
|
|
1688
|
+
if (framework === "nextjs" && layout === "src/app") return `src/${path}`;
|
|
1689
|
+
return path;
|
|
1690
|
+
}
|
|
1691
|
+
const FAILURE_NOTE = "Clivly never blocks your app from starting: a failed check-in is logged and retried on the next tick.";
|
|
1692
|
+
function nextjsHook(importPath) {
|
|
1693
|
+
return `// Clivly boot hook. Next.js calls register() once when the server starts.
|
|
1694
|
+
// ${FAILURE_NOTE}
|
|
1695
|
+
export async function register() {
|
|
1696
|
+
// The keep-alive loop is a setInterval, which does not survive on the edge
|
|
1697
|
+
// runtime — it would stop ticking after the first response and the dashboard
|
|
1698
|
+
// would show an app that checked in once and then went stale forever.
|
|
1699
|
+
if (process.env.NEXT_RUNTIME !== "nodejs") {
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1702
|
+
try {
|
|
1703
|
+
const { default: clivly } = await import("${importPath}");
|
|
1704
|
+
await clivly.start();
|
|
1705
|
+
} catch (error) {
|
|
1706
|
+
console.warn("[clivly] could not check in:", error);
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
`;
|
|
1710
|
+
}
|
|
1711
|
+
function nuxtHook(importPath) {
|
|
1712
|
+
return `import clivly from "${importPath}";
|
|
1713
|
+
|
|
1714
|
+
// Clivly boot hook. Nitro runs this once when the server starts.
|
|
1715
|
+
// ${FAILURE_NOTE}
|
|
1716
|
+
export default defineNitroPlugin(() => {
|
|
1717
|
+
clivly.start().catch((error: unknown) => {
|
|
1718
|
+
console.warn("[clivly] could not check in:", error);
|
|
1719
|
+
});
|
|
1720
|
+
});
|
|
1721
|
+
`;
|
|
1722
|
+
}
|
|
1723
|
+
function sveltekitHook(importPath) {
|
|
1724
|
+
return `import clivly from "${importPath}";
|
|
1725
|
+
|
|
1726
|
+
// Clivly boot hook. This module is evaluated once when the server starts.
|
|
1727
|
+
// ${FAILURE_NOTE}
|
|
1728
|
+
clivly.start().catch((error: unknown) => {
|
|
1729
|
+
console.warn("[clivly] could not check in:", error);
|
|
1730
|
+
});
|
|
1731
|
+
`;
|
|
1732
|
+
}
|
|
1733
|
+
const RENDERERS = {
|
|
1734
|
+
nextjs: nextjsHook,
|
|
1735
|
+
nuxt: nuxtHook,
|
|
1736
|
+
sveltekit: sveltekitHook
|
|
1737
|
+
};
|
|
1738
|
+
function renderBootHook(framework, layout) {
|
|
1739
|
+
const path = bootHookPath(framework, layout);
|
|
1740
|
+
const render = RENDERERS[framework];
|
|
1741
|
+
if (!(path && render)) throw new Error(`No standalone boot hook for framework: ${framework}`);
|
|
1742
|
+
return render(configImport(path));
|
|
1743
|
+
}
|
|
1744
|
+
const NEXT_INSTRUMENTATION_STABLE_MAJOR = 15;
|
|
1745
|
+
const LEADING_MAJOR = /(\d+)/;
|
|
1746
|
+
/** Does this Next.js version range need the experimental flag to run our hook? */
|
|
1747
|
+
function nextNeedsInstrumentationFlag(versionRange) {
|
|
1748
|
+
if (!versionRange) return false;
|
|
1749
|
+
const major = LEADING_MAJOR.exec(versionRange);
|
|
1750
|
+
if (!major?.[1]) return false;
|
|
1751
|
+
return Number(major[1]) < NEXT_INSTRUMENTATION_STABLE_MAJOR;
|
|
1752
|
+
}
|
|
1753
|
+
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:
|
|
1754
|
+
experimental: { instrumentationHook: true }`;
|
|
1755
|
+
const REMIX_BOOT_STEP = `Start Clivly when your app boots. Add this near the top of app/entry.server.tsx:
|
|
1756
|
+
import clivly from "./clivly.config";
|
|
1757
|
+
clivly.start().catch((error) => console.warn("[clivly] could not check in:", error));`;
|
|
1758
|
+
/** The boot hook as a scaffold entry, or null when this framework is patched. */
|
|
1759
|
+
function planBootHook(framework, layout) {
|
|
1760
|
+
const path = bootHookPath(framework, layout);
|
|
1761
|
+
if (!path) return null;
|
|
1762
|
+
return {
|
|
1763
|
+
path,
|
|
1764
|
+
description: "Clivly boot hook — starts check-in when your app runs",
|
|
1765
|
+
owned: true
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1768
|
+
//#endregion
|
|
1617
1769
|
//#region src/render-config.ts
|
|
1618
1770
|
const CLIVLY_ENV_KEYS = [
|
|
1619
1771
|
{
|
|
@@ -1924,10 +2076,6 @@ function routePath(framework, kind, layout = "app") {
|
|
|
1924
2076
|
}
|
|
1925
2077
|
const WINDOWS_SEP = /\\/g;
|
|
1926
2078
|
const LEADING_DOT_SLASH = /^\.\//;
|
|
1927
|
-
function configImport(routeFilePath) {
|
|
1928
|
-
const rel = relative(dirname(routeFilePath), "clivly.config").replace(WINDOWS_SEP, "/");
|
|
1929
|
-
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
1930
|
-
}
|
|
1931
2079
|
function hostImportPath(routeFilePath, appRelative, paths) {
|
|
1932
2080
|
const fromRoot = appRelative.replace(LEADING_DOT_SLASH, "");
|
|
1933
2081
|
const alias = aliasFor({
|
|
@@ -2015,6 +2163,7 @@ const COMMON_FILES = [{
|
|
|
2015
2163
|
function planScaffold(framework, layout = "app") {
|
|
2016
2164
|
const paths = ROUTE_PATHS[framework];
|
|
2017
2165
|
if (!paths) return [];
|
|
2166
|
+
const boot = planBootHook(framework, layout);
|
|
2018
2167
|
return [
|
|
2019
2168
|
{
|
|
2020
2169
|
path: applyLayout(framework, paths.tick, layout),
|
|
@@ -2026,6 +2175,7 @@ function planScaffold(framework, layout = "app") {
|
|
|
2026
2175
|
description: "Clivly auth-verify route (createAuthVerifyHandler) — finish resolveUser",
|
|
2027
2176
|
owned: true
|
|
2028
2177
|
},
|
|
2178
|
+
...boot ? [boot] : [],
|
|
2029
2179
|
...COMMON_FILES
|
|
2030
2180
|
];
|
|
2031
2181
|
}
|
|
@@ -2039,6 +2189,45 @@ function planWidgetScaffold(framework, layout = "app") {
|
|
|
2039
2189
|
}];
|
|
2040
2190
|
}
|
|
2041
2191
|
//#endregion
|
|
2192
|
+
//#region src/vite-boot-patch.ts
|
|
2193
|
+
/** Config filenames to look for, in precedence order. */
|
|
2194
|
+
const VITE_CONFIG_CANDIDATES = [
|
|
2195
|
+
"vite.config.ts",
|
|
2196
|
+
"vite.config.mts",
|
|
2197
|
+
"vite.config.js",
|
|
2198
|
+
"vite.config.mjs"
|
|
2199
|
+
];
|
|
2200
|
+
const PLUGIN_IMPORT = "import { clivlyVite } from \"clivly/vite\";";
|
|
2201
|
+
const CONFIG_IMPORT = "import clivly from \"./clivly.config\";";
|
|
2202
|
+
const PLUGIN_CALL = "clivlyVite(clivly)";
|
|
2203
|
+
const PLUGINS_ARRAY = /(^|[\s{,])plugins\s*:\s*\[/g;
|
|
2204
|
+
/** The last import line's end offset, so new imports land after the existing ones. */
|
|
2205
|
+
const IMPORT_LINE = /^\s*import\s.+?from\s+["'].+?["'];?\s*$/gm;
|
|
2206
|
+
function insertImports(source) {
|
|
2207
|
+
const last = [...source.matchAll(IMPORT_LINE)].at(-1);
|
|
2208
|
+
const block = `${PLUGIN_IMPORT}\n${CONFIG_IMPORT}`;
|
|
2209
|
+
if (!last) return `${block}\n\n${source}`;
|
|
2210
|
+
const at = (last.index ?? 0) + last[0].length;
|
|
2211
|
+
return `${source.slice(0, at)}\n${block}${source.slice(at)}`;
|
|
2212
|
+
}
|
|
2213
|
+
/**
|
|
2214
|
+
* Add the Clivly Vite plugin to an existing config.
|
|
2215
|
+
*
|
|
2216
|
+
* Returns the patched source, or `null` when the config is already patched or
|
|
2217
|
+
* is not a shape we can edit safely.
|
|
2218
|
+
*/
|
|
2219
|
+
function patchViteConfig(source) {
|
|
2220
|
+
if (source.includes(PLUGIN_CALL)) return null;
|
|
2221
|
+
const matches = [...source.matchAll(PLUGINS_ARRAY)];
|
|
2222
|
+
if (matches.length !== 1) return null;
|
|
2223
|
+
const match = matches[0];
|
|
2224
|
+
if (!match) return null;
|
|
2225
|
+
const openBracketEnd = (match.index ?? 0) + match[0].length;
|
|
2226
|
+
const rest = source.slice(openBracketEnd);
|
|
2227
|
+
const separator = rest.startsWith("\n") ? "" : " ";
|
|
2228
|
+
return insertImports(`${source.slice(0, openBracketEnd)}${PLUGIN_CALL},${separator}${rest}`);
|
|
2229
|
+
}
|
|
2230
|
+
//#endregion
|
|
2042
2231
|
//#region src/plan-mutations.ts
|
|
2043
2232
|
function fileAction(existing, next, force) {
|
|
2044
2233
|
if (existing === null) return "create";
|
|
@@ -2050,8 +2239,10 @@ function hasKey(content, key) {
|
|
|
2050
2239
|
}
|
|
2051
2240
|
function planFiles(args) {
|
|
2052
2241
|
const mutations = [];
|
|
2242
|
+
const bootPath = bootHookPath(args.framework, args.layout);
|
|
2053
2243
|
const render = (path) => {
|
|
2054
2244
|
if (path === "clivly.config.ts") return renderInitConfig(args.plan);
|
|
2245
|
+
if (bootPath !== null && path === bootPath) return renderBootHook(args.framework, args.layout);
|
|
2055
2246
|
const kind = path.includes("verify") ? "verify" : "tick";
|
|
2056
2247
|
return renderRouteFile(args.framework, kind, args.authWiring, args.layout, parseTsconfig(args.readFile(join(args.cwd, "tsconfig.json")) ?? "")?.paths);
|
|
2057
2248
|
};
|
|
@@ -2079,10 +2270,67 @@ function planEnv(args) {
|
|
|
2079
2270
|
action: envAction(addedKeys.length, existing.length)
|
|
2080
2271
|
};
|
|
2081
2272
|
}
|
|
2273
|
+
function declaredNextVersion(args) {
|
|
2274
|
+
const raw = args.readFile(join(args.cwd, "package.json"));
|
|
2275
|
+
if (!raw) return;
|
|
2276
|
+
let pkg = {};
|
|
2277
|
+
try {
|
|
2278
|
+
pkg = JSON.parse(raw);
|
|
2279
|
+
} catch {
|
|
2280
|
+
pkg = {};
|
|
2281
|
+
}
|
|
2282
|
+
return pkg.dependencies?.next ?? pkg.devDependencies?.next;
|
|
2283
|
+
}
|
|
2284
|
+
function planBootPatch(args) {
|
|
2285
|
+
if (bootHookPath(args.framework, args.layout) !== null) return {
|
|
2286
|
+
file: null,
|
|
2287
|
+
manual: args.framework === "nextjs" && nextNeedsInstrumentationFlag(declaredNextVersion(args)) ? NEXT_INSTRUMENTATION_FLAG_STEP : null
|
|
2288
|
+
};
|
|
2289
|
+
if (args.framework === "remix") return {
|
|
2290
|
+
file: null,
|
|
2291
|
+
manual: REMIX_BOOT_STEP
|
|
2292
|
+
};
|
|
2293
|
+
if (args.framework !== "tanstack-start") return {
|
|
2294
|
+
file: null,
|
|
2295
|
+
manual: null
|
|
2296
|
+
};
|
|
2297
|
+
for (const candidate of VITE_CONFIG_CANDIDATES) {
|
|
2298
|
+
const existing = args.readFile(join(args.cwd, candidate));
|
|
2299
|
+
if (existing === null) continue;
|
|
2300
|
+
const patched = patchViteConfig(existing);
|
|
2301
|
+
if (patched === null) return existing.includes("clivlyVite(clivly)") ? {
|
|
2302
|
+
file: {
|
|
2303
|
+
path: candidate,
|
|
2304
|
+
contents: existing,
|
|
2305
|
+
action: "unchanged"
|
|
2306
|
+
},
|
|
2307
|
+
manual: null
|
|
2308
|
+
} : {
|
|
2309
|
+
file: null,
|
|
2310
|
+
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), …]`
|
|
2311
|
+
};
|
|
2312
|
+
return {
|
|
2313
|
+
file: {
|
|
2314
|
+
path: candidate,
|
|
2315
|
+
contents: patched,
|
|
2316
|
+
action: "patch"
|
|
2317
|
+
},
|
|
2318
|
+
manual: null
|
|
2319
|
+
};
|
|
2320
|
+
}
|
|
2321
|
+
return {
|
|
2322
|
+
file: null,
|
|
2323
|
+
manual: null
|
|
2324
|
+
};
|
|
2325
|
+
}
|
|
2082
2326
|
function planMutations(args) {
|
|
2327
|
+
const files = planFiles(args);
|
|
2328
|
+
const boot = planBootPatch(args);
|
|
2329
|
+
if (boot.file) files.push(boot.file);
|
|
2083
2330
|
return {
|
|
2084
|
-
files
|
|
2085
|
-
env: planEnv(args)
|
|
2331
|
+
files,
|
|
2332
|
+
env: planEnv(args),
|
|
2333
|
+
manualSteps: boot.manual ? [boot.manual] : []
|
|
2086
2334
|
};
|
|
2087
2335
|
}
|
|
2088
2336
|
function planWidgetMutations(args) {
|
|
@@ -2096,6 +2344,49 @@ function planWidgetMutations(args) {
|
|
|
2096
2344
|
});
|
|
2097
2345
|
}
|
|
2098
2346
|
//#endregion
|
|
2347
|
+
//#region src/quiet-host-output.ts
|
|
2348
|
+
/**
|
|
2349
|
+
* Run `fn` with the process's stdout/stderr captured. Always restores the
|
|
2350
|
+
* original writers, including when `fn` throws — a leaked patch would silence
|
|
2351
|
+
* the whole CLI for the rest of the run.
|
|
2352
|
+
*/
|
|
2353
|
+
async function captureHostOutput(fn) {
|
|
2354
|
+
const originals = {
|
|
2355
|
+
stdout: process.stdout.write,
|
|
2356
|
+
stderr: process.stderr.write
|
|
2357
|
+
};
|
|
2358
|
+
let output = "";
|
|
2359
|
+
const capture = (chunk, encoding, callback) => {
|
|
2360
|
+
output += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
|
|
2361
|
+
const done = typeof encoding === "function" ? encoding : callback;
|
|
2362
|
+
if (typeof done === "function") done();
|
|
2363
|
+
return true;
|
|
2364
|
+
};
|
|
2365
|
+
process.stdout.write = capture;
|
|
2366
|
+
process.stderr.write = capture;
|
|
2367
|
+
try {
|
|
2368
|
+
return {
|
|
2369
|
+
result: await fn(),
|
|
2370
|
+
output
|
|
2371
|
+
};
|
|
2372
|
+
} catch (error) {
|
|
2373
|
+
if (output.trim().length > 0 && error instanceof Error) error.message = `${error.message}\n${output.trim()}`;
|
|
2374
|
+
throw error;
|
|
2375
|
+
} finally {
|
|
2376
|
+
process.stdout.write = originals.stdout;
|
|
2377
|
+
process.stderr.write = originals.stderr;
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
/**
|
|
2381
|
+
* Run `fn` quietly, discarding whatever the host printed on success. On failure
|
|
2382
|
+
* the captured output is attached to the error's message, so the host's own
|
|
2383
|
+
* explanation is not lost.
|
|
2384
|
+
*/
|
|
2385
|
+
async function quietly(fn) {
|
|
2386
|
+
const { result } = await captureHostOutput(fn);
|
|
2387
|
+
return result;
|
|
2388
|
+
}
|
|
2389
|
+
//#endregion
|
|
2099
2390
|
//#region src/probe-auth.ts
|
|
2100
2391
|
const AUTH_MODULE_PATHS = [
|
|
2101
2392
|
"auth",
|
|
@@ -2118,15 +2409,32 @@ function defaultLoader(cwd) {
|
|
|
2118
2409
|
return (specifier) => jiti.import(specifier);
|
|
2119
2410
|
}
|
|
2120
2411
|
function probeAuthInstance(cwd, load) {
|
|
2121
|
-
return probeModule({
|
|
2412
|
+
return quietly(() => probeModule({
|
|
2122
2413
|
candidates: authInstanceCandidates(cwd),
|
|
2123
2414
|
cwd,
|
|
2124
2415
|
load: load ?? defaultLoader(cwd),
|
|
2125
2416
|
match: isBetterAuthInstance
|
|
2126
|
-
});
|
|
2417
|
+
}));
|
|
2127
2418
|
}
|
|
2128
2419
|
//#endregion
|
|
2129
2420
|
//#region src/push-schema.ts
|
|
2421
|
+
/**
|
|
2422
|
+
* "1 mapping" / "3 mappings" — a count nobody has to decode. The plural is
|
|
2423
|
+
* explicit because appending "s" turns "entity" into "entitys".
|
|
2424
|
+
*/
|
|
2425
|
+
function countOf(n, singular, plural = `${singular}s`) {
|
|
2426
|
+
return `${n} ${n === 1 ? singular : plural}`;
|
|
2427
|
+
}
|
|
2428
|
+
function reportSchema(report, out) {
|
|
2429
|
+
const createdObjects = report.objectTypes.filter((o) => o.created);
|
|
2430
|
+
if (createdObjects.length > 0) out(` ✅ Created ${countOf(createdObjects.length, "custom object")}: ${createdObjects.map((o) => o.slug).join(", ")}.\n`);
|
|
2431
|
+
else if (report.objectTypes.length > 0) out(` ✅ ${countOf(report.objectTypes.length, "custom object")} already present.\n`);
|
|
2432
|
+
const created = report.mappings.filter((m) => m.created);
|
|
2433
|
+
const updated = report.mappings.filter((m) => m.updated);
|
|
2434
|
+
if (created.length > 0) out(` ✅ Mapped ${countOf(created.length, "entity", "entities")}: ${created.map((m) => m.entityKey).join(", ")}.\n`);
|
|
2435
|
+
if (updated.length > 0) out(` ✅ Updated ${countOf(updated.length, "mapping")}: ${updated.map((m) => m.entityKey).join(", ")}.\n`);
|
|
2436
|
+
if (report.mappings.length > 0 && created.length === 0 && updated.length === 0) out(` ✅ ${countOf(report.mappings.length, "mapping")} already match your config.\n`);
|
|
2437
|
+
}
|
|
2130
2438
|
async function runPushSchema(options) {
|
|
2131
2439
|
const out = options.out ?? ((line) => process.stdout.write(line));
|
|
2132
2440
|
const path = resolveConfigPath(options.cwd, options.configPath);
|
|
@@ -2154,6 +2462,29 @@ async function runPushSchema(options) {
|
|
|
2154
2462
|
}
|
|
2155
2463
|
out(" ✅ Schema reported to Clivly.\n");
|
|
2156
2464
|
out(" Integration liveness and trigger URL were refreshed too.\n");
|
|
2465
|
+
if (sdk.ensureSchema) {
|
|
2466
|
+
let report;
|
|
2467
|
+
try {
|
|
2468
|
+
report = await sdk.ensureSchema();
|
|
2469
|
+
} catch (error) {
|
|
2470
|
+
out(` ❌ Clivly refused your schema — ${error.message}\n`);
|
|
2471
|
+
return 1;
|
|
2472
|
+
}
|
|
2473
|
+
if (report === null) {
|
|
2474
|
+
out(" ⚠️ Could not declare the objects and mappings in your config.\n");
|
|
2475
|
+
out(" Run `clivly status`, then `clivly push-schema` again.\n");
|
|
2476
|
+
} else reportSchema(report, out);
|
|
2477
|
+
out(" Open Integrations → Mapping to review them.\n");
|
|
2478
|
+
return 0;
|
|
2479
|
+
}
|
|
2480
|
+
const ensured = await sdk.ensureCustomObjects?.();
|
|
2481
|
+
if (ensured === null) {
|
|
2482
|
+
out(" ⚠️ Could not create the custom objects your config declares.\n");
|
|
2483
|
+
out(" Run `clivly status`, then `clivly push-schema` again.\n");
|
|
2484
|
+
} else if (ensured && ensured.length > 0) {
|
|
2485
|
+
const created = ensured.filter((o) => o.created);
|
|
2486
|
+
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`);
|
|
2487
|
+
}
|
|
2157
2488
|
out(" Open Integrations → Mapping to map your tables.\n");
|
|
2158
2489
|
return 0;
|
|
2159
2490
|
}
|
|
@@ -2541,6 +2872,17 @@ function defaultReadConfigSource(path) {
|
|
|
2541
2872
|
return null;
|
|
2542
2873
|
}
|
|
2543
2874
|
}
|
|
2875
|
+
/**
|
|
2876
|
+
* Ping Clivly at most once per command, memoised on the context.
|
|
2877
|
+
*
|
|
2878
|
+
* `clivly status` asks two questions of one response — "did this call reach
|
|
2879
|
+
* Clivly" and "how far has this workspace got" — and they must come from the
|
|
2880
|
+
* same answer, not two calls that could disagree.
|
|
2881
|
+
*/
|
|
2882
|
+
function pingOnce(ctx) {
|
|
2883
|
+
ctx.pingResult ??= ctx.connect(ctx.apiKey, ctx.apiUrl);
|
|
2884
|
+
return ctx.pingResult;
|
|
2885
|
+
}
|
|
2544
2886
|
const REASON_HINT = {
|
|
2545
2887
|
ok: "",
|
|
2546
2888
|
invalid_api_key: "The key was rejected (401). Check CLIVLY_SECRET_KEY (or legacy CLIVLY_API_KEY).",
|
|
@@ -2552,7 +2894,7 @@ const REASON_HINT = {
|
|
|
2552
2894
|
network_error: "Could not reach Clivly. Check your network or --api-url."
|
|
2553
2895
|
};
|
|
2554
2896
|
const apiKeyStage = {
|
|
2555
|
-
id: "
|
|
2897
|
+
id: "api-key",
|
|
2556
2898
|
label: "API key present",
|
|
2557
2899
|
run(ctx) {
|
|
2558
2900
|
if (ctx.apiKey) return Promise.resolve({
|
|
@@ -2567,11 +2909,11 @@ const apiKeyStage = {
|
|
|
2567
2909
|
}
|
|
2568
2910
|
};
|
|
2569
2911
|
const reachabilityStage = {
|
|
2570
|
-
id: "
|
|
2912
|
+
id: "reachable",
|
|
2571
2913
|
label: "Clivly reachable",
|
|
2572
2914
|
needs: [apiKeyStage.id],
|
|
2573
2915
|
async run(ctx) {
|
|
2574
|
-
const result = await
|
|
2916
|
+
const result = await pingOnce(ctx);
|
|
2575
2917
|
if (result.ok) {
|
|
2576
2918
|
const name = result.org?.name ?? "(unnamed org)";
|
|
2577
2919
|
const id = result.org?.id ? ` (${result.org.id})` : "";
|
|
@@ -2705,19 +3047,57 @@ function defaultLoadSdk(path) {
|
|
|
2705
3047
|
return loadConfigSdk(path, "doctor");
|
|
2706
3048
|
}
|
|
2707
3049
|
//#endregion
|
|
3050
|
+
//#region src/workspace-section.ts
|
|
3051
|
+
const MARK$1 = {
|
|
3052
|
+
active: "⏳",
|
|
3053
|
+
blocked: "🔒",
|
|
3054
|
+
done: "✅",
|
|
3055
|
+
failed: "❌",
|
|
3056
|
+
skipped: "⏭️ "
|
|
3057
|
+
};
|
|
3058
|
+
/**
|
|
3059
|
+
* The workspace half of `clivly status`, rendered from what the server sent.
|
|
3060
|
+
*
|
|
3061
|
+
* This module decides nothing about a stage's state — it prints the verdict the
|
|
3062
|
+
* server computed. A CLI that computed its own would be a second ladder, which
|
|
3063
|
+
* is the drift this whole change removes.
|
|
3064
|
+
*
|
|
3065
|
+
* "Unavailable" and "nothing is set up" are deliberately different outcomes. An
|
|
3066
|
+
* old server, a rejected key and a dead network all mean we could not ask; none
|
|
3067
|
+
* of them means the workspace is empty.
|
|
3068
|
+
*/
|
|
3069
|
+
function workspaceSection(result) {
|
|
3070
|
+
if (!result) return {
|
|
3071
|
+
kind: "unavailable",
|
|
3072
|
+
reason: "Could not reach Clivly."
|
|
3073
|
+
};
|
|
3074
|
+
if (!result.ok) return {
|
|
3075
|
+
kind: "unavailable",
|
|
3076
|
+
reason: result.status === null ? "Could not reach Clivly (no response)." : `Clivly rejected the request — ${result.reason} (HTTP ${result.status}).`
|
|
3077
|
+
};
|
|
3078
|
+
if (!result.workspace) return {
|
|
3079
|
+
kind: "unavailable",
|
|
3080
|
+
reason: "This Clivly server does not report setup progress. Upgrade the server to see it here."
|
|
3081
|
+
};
|
|
3082
|
+
return {
|
|
3083
|
+
kind: "ladder",
|
|
3084
|
+
lines: result.workspace.steps.map((step) => ` ${MARK$1[step.state]} ${step.label}${step.evidence ? ` ${step.evidence}` : ""}`)
|
|
3085
|
+
};
|
|
3086
|
+
}
|
|
3087
|
+
//#endregion
|
|
2708
3088
|
//#region src/status.ts
|
|
2709
3089
|
/** Stage ids each legacy command maps onto. */
|
|
2710
3090
|
const ALIAS_STAGE_IDS = {
|
|
2711
3091
|
status: STATUS_STAGES.map((stage) => stage.id),
|
|
2712
|
-
ping: ["
|
|
3092
|
+
ping: ["api-key", "reachable"],
|
|
2713
3093
|
doctor: [
|
|
2714
3094
|
"config",
|
|
2715
3095
|
"runtime",
|
|
2716
3096
|
"setup-checks"
|
|
2717
3097
|
],
|
|
2718
3098
|
connect: [
|
|
2719
|
-
"
|
|
2720
|
-
"
|
|
3099
|
+
"api-key",
|
|
3100
|
+
"reachable",
|
|
2721
3101
|
"config",
|
|
2722
3102
|
"trigger-url"
|
|
2723
3103
|
]
|
|
@@ -2734,8 +3114,19 @@ const defaultConnect = (apiKey, apiUrl) => createClivlySDK({
|
|
|
2734
3114
|
apiUrl,
|
|
2735
3115
|
retry: { maxAttempts: 1 }
|
|
2736
3116
|
}).ping();
|
|
3117
|
+
/**
|
|
3118
|
+
* Exported for tests only — the rule it encodes is the point.
|
|
3119
|
+
*
|
|
3120
|
+
* A skipped check was never evaluated (nothing to check yet), which is not the
|
|
3121
|
+
* same as a warning: a warning is a working setup carrying real risk. Marking
|
|
3122
|
+
* "not applicable" with ⚠️ put a warning on every fresh install for something
|
|
3123
|
+
* the developer had not done wrong, which is exactly how people are trained to
|
|
3124
|
+
* stop reading warnings — the same reasoning `status-stages.ts` gives for
|
|
3125
|
+
* refusing to turn a stage red on a setup that works.
|
|
3126
|
+
*/
|
|
2737
3127
|
function subMark(sub) {
|
|
2738
|
-
if (sub.skipped
|
|
3128
|
+
if (sub.skipped) return "⏭️ ";
|
|
3129
|
+
if (sub.warn) return "⚠️ ";
|
|
2739
3130
|
return sub.ok ? "✅" : "❌";
|
|
2740
3131
|
}
|
|
2741
3132
|
function renderStage(result, verbose, out) {
|
|
@@ -2762,6 +3153,13 @@ async function runStatus(options) {
|
|
|
2762
3153
|
};
|
|
2763
3154
|
out("\nClivly status\n");
|
|
2764
3155
|
const results = await runLadder(stages, ctx);
|
|
3156
|
+
if (selected.has("reachable")) {
|
|
3157
|
+
const section = workspaceSection(ctx.pingResult ? await ctx.pingResult : null);
|
|
3158
|
+
out("\nWorkspace\n");
|
|
3159
|
+
if (section.kind === "ladder") for (const line of section.lines) out(`${line}\n`);
|
|
3160
|
+
else out(` ⚠️ ${section.reason}\n`);
|
|
3161
|
+
}
|
|
3162
|
+
out("\nThis project\n");
|
|
2765
3163
|
for (const result of results) renderStage(result, options.verbose ?? false, out);
|
|
2766
3164
|
const code = ladderExitCode(results);
|
|
2767
3165
|
if (code !== 0 && results.some((result) => result.state === "pending")) out("\nSetup is incomplete — finish the steps above and re-run.\n");
|
|
@@ -2791,6 +3189,7 @@ const KNOWN_COMMANDS = [
|
|
|
2791
3189
|
"init",
|
|
2792
3190
|
"status",
|
|
2793
3191
|
"push-schema",
|
|
3192
|
+
"add",
|
|
2794
3193
|
"dev",
|
|
2795
3194
|
"login",
|
|
2796
3195
|
"help",
|
|
@@ -2898,6 +3297,14 @@ function runAdd(argv, cwd, deps = defaultAddDeps) {
|
|
|
2898
3297
|
}
|
|
2899
3298
|
return 0;
|
|
2900
3299
|
}
|
|
3300
|
+
function readInstalledVersion(cwd, name) {
|
|
3301
|
+
try {
|
|
3302
|
+
const manifest = join(cwd, "node_modules", ...name.split("/"), "package.json");
|
|
3303
|
+
return JSON.parse(readFileSync(manifest, "utf8")).version ?? null;
|
|
3304
|
+
} catch {
|
|
3305
|
+
return null;
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
2901
3308
|
function readOwnVersion() {
|
|
2902
3309
|
const req = createRequire(import.meta.url);
|
|
2903
3310
|
for (const rel of ["./package.json", "../package.json"]) try {
|
|
@@ -2995,6 +3402,7 @@ function resolveInitDeps(options) {
|
|
|
2995
3402
|
install: options.installPackages ?? installPackages,
|
|
2996
3403
|
probeAuth: options.probeAuthInstance ?? probeAuthInstance,
|
|
2997
3404
|
readEnv: options.readEnv ?? defaultReadEnv,
|
|
3405
|
+
readInstalledVersion: options.readInstalledVersion ?? readInstalledVersion,
|
|
2998
3406
|
readOwnVersion: options.readOwnVersion ?? readOwnVersion,
|
|
2999
3407
|
writeEnv: options.writeEnv ?? defaultWriteEnv,
|
|
3000
3408
|
isInteractive: options.yes ? () => false : options.isInteractive ?? defaultIsInteractive,
|
|
@@ -3094,12 +3502,12 @@ function applyMutations(options, deps, mutations) {
|
|
|
3094
3502
|
deps.writeConfig(join(options.cwd, file.path), file.contents);
|
|
3095
3503
|
deps.out(` ✓ ${file.path}\n`);
|
|
3096
3504
|
}
|
|
3097
|
-
if (mutations.env.action === "skip")
|
|
3098
|
-
|
|
3099
|
-
|
|
3505
|
+
if (mutations.env.action === "skip") deps.out(" • .env already has every CLIVLY_* key — skipped\n");
|
|
3506
|
+
else {
|
|
3507
|
+
deps.writeEnv(options.cwd, mutations.env.contents);
|
|
3508
|
+
deps.out(" ✓ .env placeholders\n");
|
|
3100
3509
|
}
|
|
3101
|
-
|
|
3102
|
-
deps.out(" ✓ .env placeholders\n");
|
|
3510
|
+
for (const step of mutations.manualSteps) deps.out(`\n ! ${step}\n`);
|
|
3103
3511
|
}
|
|
3104
3512
|
const DIFF_CONTEXT_LIMIT = 40;
|
|
3105
3513
|
function renderDiff(existing, next) {
|
|
@@ -3119,6 +3527,7 @@ function previewMutations(deps, mutations, options) {
|
|
|
3119
3527
|
conflict: " (differs — --diff to see, --force to overwrite)",
|
|
3120
3528
|
create: ` (${lines} lines)`,
|
|
3121
3529
|
overwrite: ` (${lines} lines)`,
|
|
3530
|
+
patch: " (your file, plus the Clivly plugin)",
|
|
3122
3531
|
unchanged: " (already up to date)"
|
|
3123
3532
|
}[file.action];
|
|
3124
3533
|
out(` ${file.action.padEnd(9)} ${file.path}${suffix}\n`);
|
|
@@ -3176,13 +3585,17 @@ async function scaffold(options, deps, plan) {
|
|
|
3176
3585
|
});
|
|
3177
3586
|
return 0;
|
|
3178
3587
|
}
|
|
3179
|
-
const
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3588
|
+
const missing = packagesNeedingInstall(packages, (name) => deps.readInstalledVersion(options.cwd, name));
|
|
3589
|
+
if (missing.length === 0) out(`Already installed (${pm}) — nothing to fetch\n`);
|
|
3590
|
+
else {
|
|
3591
|
+
const result = await deps.install({
|
|
3592
|
+
cwd: options.cwd,
|
|
3593
|
+
pm,
|
|
3594
|
+
packages: missing
|
|
3595
|
+
});
|
|
3596
|
+
if (result.ok) out(`Installed clivly (${pm})\n`);
|
|
3597
|
+
else out(`Could not install automatically — run:\n ${result.command}\n`);
|
|
3598
|
+
}
|
|
3186
3599
|
applyMutations(options, deps, mutations);
|
|
3187
3600
|
let loginRan = false;
|
|
3188
3601
|
if (credentialsMissing(mutations.env.contents)) {
|
|
@@ -3544,4 +3957,4 @@ if (isEntrypoint(process.argv[1], import.meta.url)) main(process.argv.slice(2)).
|
|
|
3544
3957
|
process.exitCode = code;
|
|
3545
3958
|
});
|
|
3546
3959
|
//#endregion
|
|
3547
|
-
export { credentialsMissing, main, nearestCommand, runAdd, runInit, runInitFromArgv };
|
|
3960
|
+
export { KNOWN_COMMANDS, credentialsMissing, main, nearestCommand, runAdd, runInit, runInitFromArgv };
|