@getrefino/onboarding 0.1.0-rc.3 → 0.1.0-rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/inspect.d.ts CHANGED
@@ -16,4 +16,6 @@ export declare function appDirectory(inspection: RepositoryInspection): string;
16
16
  export declare function packageManagerRun(pm: PackageManager, script: string): string;
17
17
  /** Run a binary from node_modules through the package manager. */
18
18
  export declare function packageManagerExec(pm: PackageManager, command: string): string;
19
+ /** Add (or move) dependencies to exact specs, e.g. `pnpm add @getrefino/core@0.1.0`. */
20
+ export declare function packageManagerAdd(pm: PackageManager, specs: readonly string[]): string;
19
21
  export declare function packageManagerInstall(pm: PackageManager): string;
package/dist/inspect.js CHANGED
@@ -457,6 +457,20 @@ export function packageManagerExec(pm, command) {
457
457
  return `npx ${command}`;
458
458
  }
459
459
  }
460
+ /** Add (or move) dependencies to exact specs, e.g. `pnpm add @getrefino/core@0.1.0`. */
461
+ export function packageManagerAdd(pm, specs) {
462
+ const list = specs.join(" ");
463
+ switch (pm) {
464
+ case "pnpm":
465
+ return `pnpm add ${list}`;
466
+ case "yarn":
467
+ return `yarn add ${list}`;
468
+ case "bun":
469
+ return `bun add ${list}`;
470
+ default:
471
+ return `npm install ${list}`;
472
+ }
473
+ }
460
474
  export function packageManagerInstall(pm) {
461
475
  switch (pm) {
462
476
  case "pnpm":
package/dist/verify.js CHANGED
@@ -4,13 +4,13 @@
4
4
  * repository's own verification scripts.
5
5
  */
6
6
  import { spawnSync } from "node:child_process";
7
- import { join } from "node:path";
7
+ import { dirname, join, resolve } from "node:path";
8
8
  import { isContentId, parseCopy } from "@getrefino/core";
9
9
  import ts from "typescript";
10
10
  import { exists, isFile, isTestOrStoryFile, readJson, readText, rel, walkFiles } from "./fs.js";
11
11
  import { COMPARISON_SUFFIX, classifyGeneratedFiles, readGeneratedManifest } from "./generated.js";
12
12
  import { parseSource, readPathAliases, reachableFiles, resolveImport } from "./graph.js";
13
- import { LEGACY_CONFIG_FILE_NAME, appDirectory, inspectRepository } from "./inspect.js";
13
+ import { LEGACY_CONFIG_FILE_NAME, appDirectory, inspectRepository, packageManagerAdd } from "./inspect.js";
14
14
  import { isInstallMethod } from "./install-method.js";
15
15
  import { BOILERPLATE_DIR, CONFIG_FILE, TOOL_VERSION, buildPlan, verificationCommands } from "./plan.js";
16
16
  import { boilerplateFiles } from "./templates.js";
@@ -192,18 +192,44 @@ function hostedChecks(appDir, inspection, hosted, sourceFiles, rendersProvider,
192
192
  add({ id: "hosted-provider", status: "fail", message: `${file} decides edit mode on the server; a Refino-hosted site decides it in the browser.`, fix: `Render <CopyEditing content={copy}> from ${BOILERPLATE_DIR}/copy-editing without an editing prop and without next/headers.` });
193
193
  }
194
194
  }
195
+ const EXACT_VERSION = /^[=v]?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)(?:\+[0-9A-Za-z.-]+)?$/;
196
+ function normalizeVersion(value) {
197
+ return EXACT_VERSION.exec(value.trim())?.[1] ?? null;
198
+ }
199
+ function resolveRuntimePackages(inspection, appDir) {
200
+ const root = resolve(inspection.root);
201
+ return inspection.refino.packages
202
+ .filter((pkg) => pkg.name.startsWith("@getrefino/"))
203
+ .map((pkg) => {
204
+ for (let dir = resolve(appDir);; dir = dirname(dir)) {
205
+ const installed = readJson(join(dir, "node_modules", pkg.name, "package.json"));
206
+ const version = typeof installed?.version === "string" ? normalizeVersion(installed.version) : null;
207
+ if (version)
208
+ return { name: pkg.name, declared: pkg.version, version, source: "installed" };
209
+ if (dir === root || dirname(dir) === dir)
210
+ break;
211
+ }
212
+ const pinned = normalizeVersion(pkg.version);
213
+ return pinned
214
+ ? { name: pkg.name, declared: pkg.version, version: pinned, source: "pinned" }
215
+ : { name: pkg.name, declared: pkg.version, version: null, source: "range" };
216
+ });
217
+ }
195
218
  /**
196
219
  * Is the Refino runtime code in this repository the code this Refino
197
- * generates?
220
+ * generates, and do the installed packages match it?
198
221
  *
199
- * Package versions cannot answer that. Upgrading `@getrefino/*` replaces what
200
- * `node_modules` holds and leaves every file Refino copied into the
201
- * repository exactly where it was, which is how a site ran rc.2 packages
222
+ * Package versions cannot answer the first question. Upgrading `@getrefino/*`
223
+ * replaces what `node_modules` holds and leaves every file Refino copied into
224
+ * the repository exactly where it was, which is how a site ran rc.2 packages
202
225
  * against rc.1 generated code and passed verification. So the files
203
226
  * themselves are regenerated in memory and compared, and the answer is one of
204
227
  * three: current, stale but provably untouched (one command fixes it), or
205
228
  * stale and locally edited (a person has to look).
206
229
  *
230
+ * The converse happened too: rc.3 generated files next to rc.2 packages
231
+ * passed, because nothing compared the two. `package-versions` does.
232
+ *
207
233
  * Read-only, like the rest of verification: nothing here writes.
208
234
  */
209
235
  function generatedChecks(appDir, inspection, contentFile, hosted, installMethod, add) {
@@ -222,6 +248,11 @@ function generatedChecks(appDir, inspection, contentFile, hosted, installMethod,
222
248
  const present = states.filter((state) => state.status !== "create");
223
249
  if (present.length === 0)
224
250
  return;
251
+ const packages = resolveRuntimePackages(inspection, appDir);
252
+ // The normal upgrade, in the order it has to happen: packages to this
253
+ // release, then init to refresh what Refino generated.
254
+ const packagesNotCurrent = packages.filter((pkg) => pkg.version !== TOOL_VERSION);
255
+ const installPackages = packagesNotCurrent.length > 0 ? packageManagerAdd(inspection.packageManager, packagesNotCurrent.map((pkg) => `${pkg.name}@${TOOL_VERSION}`)) : null;
225
256
  const upgrade = hosted
226
257
  ? `npx @getrefino/cli@${TOOL_VERSION} init --agent --yes --refino-site ${hosted.siteId}`
227
258
  : `npx @getrefino/cli@${TOOL_VERSION} init --agent --yes`;
@@ -230,9 +261,9 @@ function generatedChecks(appDir, inspection, contentFile, hosted, installMethod,
230
261
  add({
231
262
  id: "generated-stale",
232
263
  status: "fail",
233
- message: `${stale.length} generated Refino runtime file(s) are from an earlier release, although the installed packages are not.`,
264
+ message: `${stale.length} generated Refino runtime file(s) are from an earlier release than this CLI (${TOOL_VERSION}).`,
234
265
  details: stale.map((state) => `${state.path} ${state.from ?? "unknown"} → ${state.to}`),
235
- fix: `Run \`${upgrade}\`. Each file listed is byte-identical to what Refino wrote, so it is refreshed in place; nothing you wrote is touched.`,
266
+ fix: `${installPackages ? `Run \`${installPackages}\`, then \`${upgrade}\`` : `Run \`${upgrade}\``}. Each file listed is byte-identical to what Refino wrote, so it is refreshed in place; nothing you wrote is touched.`,
236
267
  });
237
268
  }
238
269
  const review = present.filter((state) => state.status === "review");
@@ -259,6 +290,42 @@ function generatedChecks(appDir, inspection, contentFile, hosted, installMethod,
259
290
  : {}),
260
291
  });
261
292
  }
293
+ // Which release the generated integration is. Only files whose bytes are a
294
+ // known release's output say so; an edited file's recorded version is where
295
+ // it started, not what it is. Files from more than one release have no
296
+ // single version to compare against, and generated-stale already fails.
297
+ const generatedVersions = new Set(present.filter((state) => state.status === "current" || state.status === "update").map((state) => state.from));
298
+ const generatedVersion = generatedVersions.size === 1 ? [...generatedVersions][0] : null;
299
+ if (generatedVersion === null || packages.length === 0)
300
+ return;
301
+ const describe = (pkg) => pkg.source === "installed" ? `${pkg.name} ${pkg.version} (installed)` : pkg.source === "pinned" ? `${pkg.name} ${pkg.version} (pinned in package.json, not installed)` : `${pkg.name} "${pkg.declared}" (a range in package.json, not installed)`;
302
+ const mismatched = packages.filter((pkg) => pkg.version !== null && pkg.version !== generatedVersion);
303
+ const unresolved = packages.filter((pkg) => pkg.version === null);
304
+ const fix = [
305
+ installPackages ? `Run \`${installPackages}\`` : null,
306
+ generatedVersion !== TOOL_VERSION ? `${installPackages ? "then " : "Run "}\`${upgrade}\`` : null,
307
+ ].filter(Boolean).join(", ");
308
+ if (mismatched.length > 0) {
309
+ add({
310
+ id: "package-versions",
311
+ status: "fail",
312
+ message: `Installed Refino packages do not match the generated integration (${generatedVersion}).`,
313
+ details: [...mismatched, ...unresolved].map(describe),
314
+ fix: `${fix || `Run \`${upgrade}\``}, so the packages and the generated integration are the same release.`,
315
+ });
316
+ }
317
+ else if (unresolved.length > 0) {
318
+ add({
319
+ id: "package-versions",
320
+ status: "warn",
321
+ message: `Cannot confirm the installed Refino package versions match the generated integration (${generatedVersion}).`,
322
+ details: unresolved.map(describe),
323
+ fix: `Install dependencies, or pin them: \`${packageManagerAdd(inspection.packageManager, unresolved.map((pkg) => `${pkg.name}@${generatedVersion}`))}\`.`,
324
+ });
325
+ }
326
+ else {
327
+ add({ id: "package-versions", status: "pass", message: `Installed Refino packages match the generated integration (${generatedVersion}).` });
328
+ }
262
329
  }
263
330
  export function verifyIntegration(rootPath, options = {}) {
264
331
  const inspection = inspectRepository(rootPath, options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getrefino/onboarding",
3
- "version": "0.1.0-rc.3",
3
+ "version": "0.1.0-rc.4",
4
4
  "description": "The engine behind `npx @getrefino/cli init`: deterministic repository inspection, copy discovery, migration planning, agent instructions and verification for adding Refino to an existing React site. Development tooling only; never part of a site's runtime.",
5
5
  "keywords": [
6
6
  "refino",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "typescript": ">=5.5 <7",
41
- "@getrefino/core": "0.1.0-rc.3"
41
+ "@getrefino/core": "0.1.0-rc.4"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/node": "22.20.2",