@getrefino/onboarding 0.1.0-rc.1

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.
@@ -0,0 +1,471 @@
1
+ /**
2
+ * Deterministic repository inspection. Reads files only; never runs the
3
+ * project, never calls a model. Everything reported here can be checked by
4
+ * opening the files it names.
5
+ */
6
+ import { spawnSync } from "node:child_process";
7
+ import { readdirSync } from "node:fs";
8
+ import { join, resolve } from "node:path";
9
+ import { exists, findUp, isDirectory, isFile, readJson, readText, rel, toPosix, walkFiles } from "./fs.js";
10
+ import { discoverRoutes } from "./graph.js";
11
+ function dep(pkg, name) {
12
+ if (pkg.dependencies?.[name])
13
+ return { name, version: pkg.dependencies[name], kind: "dependencies" };
14
+ if (pkg.devDependencies?.[name])
15
+ return { name, version: pkg.devDependencies[name], kind: "devDependencies" };
16
+ return null;
17
+ }
18
+ function hasDep(pkg, name) {
19
+ return dep(pkg, name) !== null;
20
+ }
21
+ function detectPackageManager(root, pkg) {
22
+ const declared = pkg?.packageManager?.split("@")[0];
23
+ if (declared === "pnpm" || declared === "npm" || declared === "yarn" || declared === "bun")
24
+ return declared;
25
+ // Lockfiles live at the workspace root, which may be above the app being inspected.
26
+ const lock = findUp(root, ["pnpm-lock.yaml", "yarn.lock", "bun.lock", "bun.lockb", "package-lock.json"]);
27
+ if (!lock)
28
+ return "unknown";
29
+ if (lock.endsWith("pnpm-lock.yaml"))
30
+ return "pnpm";
31
+ if (lock.endsWith("yarn.lock"))
32
+ return "yarn";
33
+ if (lock.endsWith("bun.lock") || lock.endsWith("bun.lockb"))
34
+ return "bun";
35
+ return "npm";
36
+ }
37
+ function detectMonorepo(root, pkg) {
38
+ const patterns = [];
39
+ let tool = null;
40
+ const workspaceYaml = readText(join(root, "pnpm-workspace.yaml"));
41
+ if (workspaceYaml) {
42
+ // pnpm-workspace.yaml also carries settings such as onlyBuiltDependencies; only a
43
+ // `packages:` list makes it a workspace.
44
+ const workspacePatterns = pnpmWorkspacePackages(workspaceYaml);
45
+ if (workspacePatterns.length > 0) {
46
+ tool = "pnpm";
47
+ patterns.push(...workspacePatterns);
48
+ }
49
+ }
50
+ const workspaces = pkg?.workspaces;
51
+ if (workspaces) {
52
+ tool ??= exists(join(root, "yarn.lock")) ? "yarn" : "npm";
53
+ patterns.push(...(Array.isArray(workspaces) ? workspaces : (workspaces.packages ?? [])));
54
+ }
55
+ if (exists(join(root, "turbo.json")))
56
+ tool ??= "turbo";
57
+ if (exists(join(root, "lerna.json")))
58
+ tool ??= "lerna";
59
+ if (exists(join(root, "nx.json")))
60
+ tool ??= "nx";
61
+ if (!tool && patterns.length === 0)
62
+ return { detected: false, tool: null, apps: [] };
63
+ const apps = [];
64
+ for (const pattern of patterns) {
65
+ const base = pattern.replace(/\/?\*+$/, "");
66
+ const baseDir = join(root, base);
67
+ if (!isDirectory(baseDir))
68
+ continue;
69
+ if (pattern.endsWith("*")) {
70
+ for (const name of walkDirs(baseDir)) {
71
+ if (exists(join(baseDir, name, "package.json")))
72
+ apps.push(toPosix(join(base, name)));
73
+ }
74
+ }
75
+ else if (exists(join(baseDir, "package.json"))) {
76
+ apps.push(toPosix(base));
77
+ }
78
+ }
79
+ return { detected: true, tool, apps: apps.sort() };
80
+ }
81
+ /** The `packages:` entries of a pnpm-workspace.yaml (block list or inline array). */
82
+ export function pnpmWorkspacePackages(yaml) {
83
+ const inline = yaml.match(/^packages\s*:\s*\[([^\]]*)\]/m);
84
+ if (inline) {
85
+ return inline[1]
86
+ .split(",")
87
+ .map((item) => item.trim().replace(/^["']|["']$/g, ""))
88
+ .filter((item) => item.length > 0);
89
+ }
90
+ const lines = yaml.split(/\r?\n/);
91
+ const start = lines.findIndex((line) => /^packages\s*:\s*$/.test(line));
92
+ if (start === -1)
93
+ return [];
94
+ const patterns = [];
95
+ for (const line of lines.slice(start + 1)) {
96
+ if (/^\s*(#|$)/.test(line))
97
+ continue;
98
+ const item = line.match(/^\s+-\s*["']?([^"'#]+?)["']?\s*$/);
99
+ if (!item)
100
+ break;
101
+ patterns.push(item[1].trim());
102
+ }
103
+ return patterns;
104
+ }
105
+ function walkDirs(dir) {
106
+ try {
107
+ return readdirSync(dir).filter((name) => !name.startsWith(".") && isDirectory(join(dir, name)));
108
+ }
109
+ catch {
110
+ return [];
111
+ }
112
+ }
113
+ /** Pick the app to onboard: the workspace package that looks most like a React site. */
114
+ function chooseAppRoot(root, monorepo, rootPkg) {
115
+ if (!monorepo.detected)
116
+ return ".";
117
+ if (rootPkg && (hasDep(rootPkg, "next") || hasDep(rootPkg, "react")))
118
+ return ".";
119
+ let best = null;
120
+ for (const app of monorepo.apps) {
121
+ const pkg = readJson(join(root, app, "package.json"));
122
+ if (!pkg)
123
+ continue;
124
+ let score = 0;
125
+ if (hasDep(pkg, "next"))
126
+ score += 3;
127
+ if (hasDep(pkg, "vite"))
128
+ score += 2;
129
+ if (hasDep(pkg, "react"))
130
+ score += 1;
131
+ if (app.startsWith("apps/"))
132
+ score += 1;
133
+ if (score > 0 && (!best || score > best.score))
134
+ best = { path: app, score };
135
+ }
136
+ return best?.path ?? ".";
137
+ }
138
+ function detectScripts(pkg) {
139
+ const scripts = pkg?.scripts ?? {};
140
+ const pick = (names) => names.find((name) => scripts[name] !== undefined);
141
+ const result = {};
142
+ const build = pick(["build"]);
143
+ const test = pick(["test", "test:unit", "vitest", "jest"]);
144
+ const typecheck = pick(["typecheck", "type-check", "types", "check-types", "tsc", "check"]);
145
+ const lint = pick(["lint", "eslint"]);
146
+ const dev = pick(["dev", "start"]);
147
+ if (build)
148
+ result.build = build;
149
+ if (test)
150
+ result.test = test;
151
+ if (typecheck)
152
+ result.typecheck = typecheck;
153
+ if (lint)
154
+ result.lint = lint;
155
+ if (dev)
156
+ result.dev = dev;
157
+ return result;
158
+ }
159
+ const AUTH_LIBRARIES = [
160
+ "next-auth",
161
+ "@auth/core",
162
+ "@auth/nextjs",
163
+ "@clerk/nextjs",
164
+ "@clerk/clerk-react",
165
+ "@supabase/ssr",
166
+ "@supabase/supabase-js",
167
+ "@supabase/auth-helpers-nextjs",
168
+ "lucia",
169
+ "iron-session",
170
+ "jose",
171
+ "jsonwebtoken",
172
+ "passport",
173
+ "express-session",
174
+ "firebase",
175
+ "firebase-admin",
176
+ "@kinde-oss/kinde-auth-nextjs",
177
+ "@auth0/nextjs-auth0",
178
+ "@workos-inc/authkit-nextjs",
179
+ "better-auth",
180
+ ];
181
+ function detectAuth(appDir, pkg, sourceDir) {
182
+ const libraries = pkg ? AUTH_LIBRARIES.filter((name) => hasDep(pkg, name)) : [];
183
+ const middlewareCandidates = ["proxy.ts", "proxy.js", "middleware.ts", "middleware.js"].flatMap((name) => [
184
+ join(appDir, name),
185
+ join(appDir, sourceDir, name),
186
+ ]);
187
+ const middlewareFile = middlewareCandidates.find((path) => isFile(path)) ?? null;
188
+ const notes = [];
189
+ if (libraries.length === 0) {
190
+ notes.push("No authentication library detected. The generated password login (refino/editor-auth) is the default.");
191
+ }
192
+ else {
193
+ notes.push(`Existing authentication: ${libraries.join(", ")}. Prefer gating edit mode with it (an "is this the owner?" check) instead of adding a second login.`);
194
+ }
195
+ if (middlewareFile)
196
+ notes.push(`Request middleware exists at ${rel(appDir, middlewareFile)}; do not put edit-mode authorization there alone, every privileged route must re-check.`);
197
+ return { libraries, middlewareFile: middlewareFile ? rel(appDir, middlewareFile) : null, notes };
198
+ }
199
+ const NEXT_CONFIG_FILES = ["next.config.js", "next.config.mjs", "next.config.cjs", "next.config.ts", "next.config.mts"];
200
+ /** Read the facts we need from next.config.* without executing it. */
201
+ function detectNextConfig(appDir) {
202
+ const file = NEXT_CONFIG_FILES.find((name) => isFile(join(appDir, name)));
203
+ if (!file)
204
+ return { file: null, output: null };
205
+ const text = readText(join(appDir, file)) ?? "";
206
+ const output = text.match(/\boutput\s*:\s*["'`](export|standalone)["'`]/)?.[1] ?? null;
207
+ return { file, output: output === "export" || output === "standalone" ? output : null };
208
+ }
209
+ /** Directories where the host's serverless functions live, when present. */
210
+ function detectServerlessDir(appDir) {
211
+ return ["functions", "netlify/functions"].find((dir) => isDirectory(join(appDir, dir))) ?? null;
212
+ }
213
+ function detectServer(appDir, framework, router, sourceDir, pkg, nextConfig) {
214
+ if (framework === "next") {
215
+ if (nextConfig.output === "export") {
216
+ const serverlessDir = detectServerlessDir(appDir);
217
+ return {
218
+ capable: false,
219
+ kind: "static",
220
+ apiDir: serverlessDir,
221
+ notes: [`${nextConfig.file} sets output: "export": the site is exported as static HTML. Route handlers, cookies() and dynamic rendering are not available.`],
222
+ selfHosted: [
223
+ "The copy endpoint must be a serverless function of the host, and edit mode must be detected in the browser via GET /api/edit-session.",
224
+ serverlessDir ? `A ${serverlessDir}/ directory exists; the serverless endpoint belongs there.` : "No serverless functions directory exists yet.",
225
+ ],
226
+ };
227
+ }
228
+ const appApi = [join(appDir, "app/api"), join(appDir, "src/app/api")].find((p) => isDirectory(p));
229
+ const pagesApi = [join(appDir, "pages/api"), join(appDir, "src/pages/api")].find((p) => isDirectory(p));
230
+ if (router === "next-app") {
231
+ return { capable: true, kind: "next-route-handlers", apiDir: appApi ? rel(appDir, appApi) : null, notes: ["The app router supports route handlers (app/api), cookies() and per-request rendering."], selfHosted: ["Route handlers under app/api can host the copy endpoint."] };
232
+ }
233
+ return { capable: true, kind: "next-api-routes", apiDir: pagesApi ? rel(appDir, pagesApi) : null, notes: ["The pages router supports API routes (pages/api) and per-request rendering."], selfHosted: ["API routes under pages/api can host the copy endpoint."] };
234
+ }
235
+ const serverLibs = ["express", "hono", "fastify", "koa", "@hono/node-server", "h3", "nitropack"].filter((name) => pkg && hasDep(pkg, name));
236
+ const serverDirs = ["server", "api", "functions", "netlify/functions", join(sourceDir, "server")].filter((dir) => isDirectory(join(appDir, dir)));
237
+ if (serverLibs.length > 0 || serverDirs.length > 0) {
238
+ return {
239
+ capable: true,
240
+ kind: "custom-server",
241
+ apiDir: serverDirs[0] ?? null,
242
+ notes: [`Server code detected (${[...serverLibs, ...serverDirs].join(", ")}).`],
243
+ selfHosted: ["The copy endpoint must live there."],
244
+ };
245
+ }
246
+ return {
247
+ capable: false,
248
+ kind: "static",
249
+ apiDir: null,
250
+ notes: ["No server code detected."],
251
+ selfHosted: ["Saving copy needs one authenticated endpoint; add a serverless function (Vercel/Netlify/Cloudflare) or a small Node server."],
252
+ };
253
+ }
254
+ function detectHosting(root, appDir, pkg) {
255
+ const evidence = [];
256
+ let provider = null;
257
+ const check = (file, name) => {
258
+ for (const base of [appDir, root]) {
259
+ if (exists(join(base, file))) {
260
+ evidence.push(rel(root, join(base, file)));
261
+ provider ??= name;
262
+ return;
263
+ }
264
+ }
265
+ };
266
+ check("vercel.json", "vercel");
267
+ check("netlify.toml", "netlify");
268
+ check("wrangler.toml", "cloudflare");
269
+ check("wrangler.jsonc", "cloudflare");
270
+ check("wrangler.json", "cloudflare");
271
+ check("fly.toml", "fly");
272
+ check("render.yaml", "render");
273
+ check("amplify.yml", "aws-amplify");
274
+ check("firebase.json", "firebase");
275
+ check("Dockerfile", "docker");
276
+ // Project links written by the hosts' CLIs (git-ignored in most repositories, present on a linked checkout).
277
+ check(".vercel/project.json", "vercel");
278
+ check(".netlify/state.json", "netlify");
279
+ // Cloudflare Pages needs no config file: a functions/ directory is its Functions convention.
280
+ if (isDirectory(join(appDir, "functions")) && !isDirectory(join(appDir, "netlify"))) {
281
+ evidence.push("functions/ (Cloudflare Pages Functions directory)");
282
+ provider ??= "cloudflare";
283
+ }
284
+ for (const name of ["_headers", "_redirects"]) {
285
+ const publicDir = isDirectory(join(appDir, "public")) ? "public" : "static";
286
+ if (isFile(join(appDir, publicDir, name)))
287
+ evidence.push(`${publicDir}/${name} (Cloudflare Pages or Netlify static hosting)`);
288
+ }
289
+ if (pkg) {
290
+ for (const [name, host] of [["@opennextjs/cloudflare", "cloudflare"], ["@cloudflare/next-on-pages", "cloudflare"], ["@netlify/plugin-nextjs", "netlify"], ["@vercel/analytics", "vercel"]]) {
291
+ if (hasDep(pkg, name)) {
292
+ evidence.push(`dependency ${name}`);
293
+ provider ??= host;
294
+ }
295
+ }
296
+ }
297
+ return { provider, evidence };
298
+ }
299
+ function detectGit(root) {
300
+ const gitDir = join(root, ".git");
301
+ if (!exists(gitDir))
302
+ return { isRepository: false, branch: null, clean: null, remote: null, github: null };
303
+ const run = (args) => {
304
+ const result = spawnSync("git", args, { cwd: root, encoding: "utf8" });
305
+ return result.status === 0 ? result.stdout.trim() : null;
306
+ };
307
+ const branch = run(["rev-parse", "--abbrev-ref", "HEAD"]);
308
+ const status = run(["status", "--porcelain"]);
309
+ const remote = run(["remote", "get-url", "origin"]);
310
+ const match = remote?.match(/github\.com[:/]([^/]+)\/([^/]+?)(?:\.git)?$/);
311
+ return {
312
+ isRepository: true,
313
+ branch,
314
+ clean: status === null ? null : status.length === 0,
315
+ remote,
316
+ github: match ? { owner: match[1], repo: match[2] } : null,
317
+ };
318
+ }
319
+ /** The runtime packages a migrated site depends on, and the names they had before the Refino rename. */
320
+ export const REFINO_PACKAGES = ["@getrefino/core", "@getrefino/react", "@getrefino/github"];
321
+ export const LEGACY_PACKAGES = ["@inline-copy/core", "@inline-copy/react", "@inline-copy/github"];
322
+ export const CONFIG_FILE_NAME = "refino.config.json";
323
+ export const LEGACY_CONFIG_FILE_NAME = "inline-copy.config.json";
324
+ function detectContentDirs(appDir) {
325
+ return ["content", "src/content", "data", "src/data", "copy", "src/copy", "locales", "src/locales"].filter((dir) => isDirectory(join(appDir, dir)));
326
+ }
327
+ function detectExistingIntegration(appDir, pkg) {
328
+ // Legacy names are still recognised so a site onboarded before the Refino
329
+ // rename is inspected correctly and can be told what to upgrade.
330
+ const packages = pkg
331
+ ? [...REFINO_PACKAGES, ...LEGACY_PACKAGES].map((name) => dep(pkg, name)).filter((d) => d !== null)
332
+ : [];
333
+ const configFile = [CONFIG_FILE_NAME, LEGACY_CONFIG_FILE_NAME].map((name) => join(appDir, name)).find((path) => isFile(path)) ?? null;
334
+ const config = configFile ? readJson(configFile) : null;
335
+ const contentCandidates = [config?.contentFile, "content/copy.json", "src/content/copy.json", "copy.json", "src/copy.json"].filter((p) => typeof p === "string");
336
+ const contentFile = contentCandidates.map((p) => join(appDir, p)).find((path) => isFile(path)) ?? null;
337
+ const providerFiles = [];
338
+ const endpointFiles = [];
339
+ const editableTextFiles = [];
340
+ for (const file of walkFiles(appDir)) {
341
+ const text = readText(file);
342
+ if (!text || !(text.includes("refino") || text.includes("inline-copy")))
343
+ continue;
344
+ const relative = rel(appDir, file);
345
+ if (text.includes("RefinoProvider") || text.includes("InlineCopyProvider"))
346
+ providerFiles.push(relative);
347
+ if (text.includes("createContentApi"))
348
+ endpointFiles.push(relative);
349
+ if (text.includes("EditableText"))
350
+ editableTextFiles.push(relative);
351
+ }
352
+ return {
353
+ packages,
354
+ contentFile: contentFile ? rel(appDir, contentFile) : null,
355
+ configFile: configFile ? rel(appDir, configFile) : null,
356
+ providerFiles,
357
+ endpointFiles,
358
+ editableTextFiles,
359
+ };
360
+ }
361
+ export function inspectRepository(rootPath, options = {}) {
362
+ const root = resolve(rootPath);
363
+ const rootPkg = readJson(join(root, "package.json"));
364
+ const monorepo = detectMonorepo(root, rootPkg);
365
+ const appRoot = options.app ? toPosix(options.app).replace(/\/+$/, "") || "." : chooseAppRoot(root, monorepo, rootPkg);
366
+ const appDir = resolve(root, appRoot);
367
+ const packageJsonPath = join(appDir, "package.json");
368
+ const pkg = readJson(packageJsonPath);
369
+ const effectivePkg = pkg ?? rootPkg;
370
+ const react = effectivePkg ? dep(effectivePkg, "react") : null;
371
+ const next = effectivePkg ? dep(effectivePkg, "next") : null;
372
+ const vite = effectivePkg ? dep(effectivePkg, "vite") : null;
373
+ const typescriptDep = effectivePkg ? dep(effectivePkg, "typescript") : null;
374
+ const hasTsconfig = exists(join(appDir, "tsconfig.json"));
375
+ const language = typescriptDep || hasTsconfig ? "typescript" : "javascript";
376
+ const sourceDir = isDirectory(join(appDir, "src")) && !isDirectory(join(appDir, "app")) && !isDirectory(join(appDir, "pages")) ? "src" : ".";
377
+ let framework = "unsupported";
378
+ const unsupportedReasons = [];
379
+ if (!effectivePkg)
380
+ unsupportedReasons.push(`No package.json found at ${rel(root, packageJsonPath)}.`);
381
+ if (next && react)
382
+ framework = "next";
383
+ else if (vite && react && (effectivePkg && hasDep(effectivePkg, "@vitejs/plugin-react")) !== false)
384
+ framework = "vite-react";
385
+ else if (react)
386
+ framework = "react";
387
+ else if (effectivePkg)
388
+ unsupportedReasons.push("React is not a dependency; Refino v1 supports React-based sites only.");
389
+ const routing = discoverRoutes(appDir, framework, sourceDir);
390
+ const router = routing.router;
391
+ if (framework === "next" && router === "unknown")
392
+ unsupportedReasons.push("Next.js detected but neither an app/ nor a pages/ directory was found.");
393
+ if ((framework === "react" || framework === "vite-react") && !routing.entry)
394
+ unsupportedReasons.push("Could not find the React entry file (expected src/main.tsx, src/index.tsx or similar).");
395
+ const scripts = detectScripts(effectivePkg);
396
+ const auth = detectAuth(appDir, effectivePkg, sourceDir);
397
+ const nextConfig = framework === "next" ? detectNextConfig(appDir) : { file: null, output: null };
398
+ const server = detectServer(appDir, framework, router, sourceDir, effectivePkg, nextConfig);
399
+ const supported = framework !== "unsupported" && unsupportedReasons.length === 0;
400
+ return {
401
+ root: toPosix(root),
402
+ appRoot,
403
+ monorepo,
404
+ packageManager: detectPackageManager(root, rootPkg ?? pkg),
405
+ packageJson: pkg ? rel(root, packageJsonPath) : rootPkg ? "package.json" : null,
406
+ packageName: effectivePkg?.name ?? null,
407
+ framework,
408
+ router,
409
+ language,
410
+ react,
411
+ next,
412
+ nextConfig,
413
+ vite,
414
+ typescript: typescriptDep,
415
+ sourceDir,
416
+ routesDir: routing.routesDir,
417
+ routes: routing.routes,
418
+ layouts: routing.layouts,
419
+ entry: routing.entry,
420
+ scripts,
421
+ contentDirs: detectContentDirs(appDir),
422
+ refino: detectExistingIntegration(appDir, effectivePkg),
423
+ auth,
424
+ server,
425
+ hosting: detectHosting(root, appDir, effectivePkg),
426
+ git: detectGit(root),
427
+ supported,
428
+ unsupportedReasons,
429
+ };
430
+ }
431
+ /** Absolute path of the app directory for an inspection. */
432
+ export function appDirectory(inspection) {
433
+ return resolve(inspection.root, inspection.appRoot);
434
+ }
435
+ export function packageManagerRun(pm, script) {
436
+ switch (pm) {
437
+ case "pnpm":
438
+ return `pnpm ${script}`;
439
+ case "yarn":
440
+ return `yarn ${script}`;
441
+ case "bun":
442
+ return `bun run ${script}`;
443
+ default:
444
+ return `npm run ${script}`;
445
+ }
446
+ }
447
+ /** Run a binary from node_modules through the package manager. */
448
+ export function packageManagerExec(pm, command) {
449
+ switch (pm) {
450
+ case "pnpm":
451
+ return `pnpm exec ${command}`;
452
+ case "yarn":
453
+ return `yarn ${command}`;
454
+ case "bun":
455
+ return `bunx ${command}`;
456
+ default:
457
+ return `npx ${command}`;
458
+ }
459
+ }
460
+ export function packageManagerInstall(pm) {
461
+ switch (pm) {
462
+ case "pnpm":
463
+ return "pnpm install";
464
+ case "yarn":
465
+ return "yarn install";
466
+ case "bun":
467
+ return "bun install";
468
+ default:
469
+ return "npm install";
470
+ }
471
+ }
package/dist/plan.d.ts ADDED
@@ -0,0 +1,43 @@
1
+ import type { HostIntegration, HostKind, MigrationPlan, PlanOptions, RefinoSite, RepositoryInspection, ScanResult } from "./types.js";
2
+ export declare const TOOL_NAME = "refino";
3
+ /**
4
+ * The published version of this package. A prerelease build names the commit
5
+ * it came from, so plans, instructions and the dependency pins a site
6
+ * receives say exactly which build produced them.
7
+ */
8
+ export declare const TOOL_VERSION: string;
9
+ /**
10
+ * Dependency range for the runtime packages. Any prerelease (a smoke build or
11
+ * a release candidate) is pinned exactly, so a site records the one build it
12
+ * was tested against and never floats onto a later prerelease or onto the
13
+ * eventual stable release. Stable versions get a caret.
14
+ */
15
+ export declare function dependencyRange(version?: string): string;
16
+ export declare const PLAN_DIR = ".refino";
17
+ export declare const PLAN_FILE = ".refino/plan.json";
18
+ export declare const INSTRUCTIONS_FILE = ".refino/AGENT_INSTRUCTIONS.md";
19
+ export declare const CONFIG_FILE = "refino.config.json";
20
+ export declare const BOILERPLATE_DIR = "refino";
21
+ export declare function defaultContentFile(inspection: RepositoryInspection): string;
22
+ /** Validate and normalize the Refino connection given on the command line. */
23
+ export declare function resolveRefinoSite(option: PlanOptions["refino"]): RefinoSite | null;
24
+ export declare function hostLabel(kind: HostKind): string;
25
+ /**
26
+ * Choose the host path that serves /api/copy and /api/edit-session. The
27
+ * behaviour is generated once (refino/request-handlers); this decides
28
+ * which thin wrapper files the host expects, and whether the tool can write
29
+ * them itself. Inspection facts only; nothing site-specific.
30
+ */
31
+ export declare function hostIntegration(inspection: RepositoryInspection, refino?: RefinoSite | null): HostIntegration;
32
+ /** Where a serverless copy endpoint lives for the detected host. */
33
+ export declare function serverlessEndpointPath(inspection: RepositoryInspection, _ext: string): {
34
+ path: string;
35
+ note: string;
36
+ };
37
+ export declare function integrationPoints(inspection: RepositoryInspection, refino?: RefinoSite | null): MigrationPlan["integration"];
38
+ export declare function verificationCommands(inspection: RepositoryInspection): string[];
39
+ export declare function buildPlan(inspection: RepositoryInspection, scan: ScanResult, options?: PlanOptions): MigrationPlan;
40
+ /** Convenience: inspect + scan + plan in one call. */
41
+ export declare function planMigration(inspection: RepositoryInspection, options?: PlanOptions): MigrationPlan;
42
+ /** The copy-file content a plan would create: selected candidates only, in scan order. */
43
+ export declare function planContent(plan: MigrationPlan): Record<string, string>;