@getrefino/onboarding 0.1.0-rc.2 → 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/verify.js CHANGED
@@ -4,13 +4,16 @@
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
+ import { COMPARISON_SUFFIX, classifyGeneratedFiles, readGeneratedManifest } from "./generated.js";
11
12
  import { parseSource, readPathAliases, reachableFiles, resolveImport } from "./graph.js";
12
- import { LEGACY_CONFIG_FILE_NAME, appDirectory, inspectRepository } from "./inspect.js";
13
- import { BOILERPLATE_DIR, CONFIG_FILE, verificationCommands } from "./plan.js";
13
+ import { LEGACY_CONFIG_FILE_NAME, appDirectory, inspectRepository, packageManagerAdd } from "./inspect.js";
14
+ import { isInstallMethod } from "./install-method.js";
15
+ import { BOILERPLATE_DIR, CONFIG_FILE, TOOL_VERSION, buildPlan, verificationCommands } from "./plan.js";
16
+ import { boilerplateFiles } from "./templates.js";
14
17
  const SECRET_NAMES = ["COPY_GITHUB_TOKEN", "EDITOR_SESSION_SECRET", "EDITOR_PASSWORD"];
15
18
  const SERVER_ONLY_IMPORTS = ["@getrefino/github", "@getrefino/core/local-file"];
16
19
  function collectEditableTextUses(appDir, files) {
@@ -189,6 +192,141 @@ function hostedChecks(appDir, inspection, hosted, sourceFiles, rendersProvider,
189
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.` });
190
193
  }
191
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
+ }
218
+ /**
219
+ * Is the Refino runtime code in this repository the code this Refino
220
+ * generates, and do the installed packages match it?
221
+ *
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
225
+ * against rc.1 generated code and passed verification. So the files
226
+ * themselves are regenerated in memory and compared, and the answer is one of
227
+ * three: current, stale but provably untouched (one command fixes it), or
228
+ * stale and locally edited (a person has to look).
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
+ *
233
+ * Read-only, like the rest of verification: nothing here writes.
234
+ */
235
+ function generatedChecks(appDir, inspection, contentFile, hosted, installMethod, add) {
236
+ let states;
237
+ try {
238
+ const plan = buildPlan(inspection, { filesScanned: [], candidates: [] }, {
239
+ contentFile,
240
+ ...(hosted ? { refino: { siteId: hosted.siteId, appUrl: hosted.appUrl, ...(isInstallMethod(installMethod) ? { installMethod } : {}) } } : {}),
241
+ });
242
+ states = classifyGeneratedFiles(appDir, boilerplateFiles(plan), readGeneratedManifest(appDir));
243
+ }
244
+ catch {
245
+ // A repository this build cannot plan for has no generated files to judge.
246
+ return;
247
+ }
248
+ const present = states.filter((state) => state.status !== "create");
249
+ if (present.length === 0)
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;
256
+ const upgrade = hosted
257
+ ? `npx @getrefino/cli@${TOOL_VERSION} init --agent --yes --refino-site ${hosted.siteId}`
258
+ : `npx @getrefino/cli@${TOOL_VERSION} init --agent --yes`;
259
+ const stale = present.filter((state) => state.status === "update");
260
+ if (stale.length > 0) {
261
+ add({
262
+ id: "generated-stale",
263
+ status: "fail",
264
+ message: `${stale.length} generated Refino runtime file(s) are from an earlier release than this CLI (${TOOL_VERSION}).`,
265
+ details: stale.map((state) => `${state.path} ${state.from ?? "unknown"} → ${state.to}`),
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.`,
267
+ });
268
+ }
269
+ const review = present.filter((state) => state.status === "review");
270
+ if (review.length > 0) {
271
+ // A restyled /edit page is a normal thing to own; Refino's own runtime
272
+ // modules are not, and a stale one of those is a real problem.
273
+ const machine = review.some((state) => state.ownership === "machine");
274
+ add({
275
+ id: "generated-review",
276
+ status: machine ? "fail" : "warn",
277
+ message: `${review.length} generated Refino file(s) have local changes and are from an earlier release, so Refino will not refresh them.`,
278
+ details: review.map((state) => `${state.path} ${state.reason}`),
279
+ fix: `Run \`${upgrade}\`: it leaves these files alone and writes Refino's current version beside each one as \`<file>${COMPARISON_SUFFIX}\`. Move your changes onto that version, replace the original with it, delete the \`${COMPARISON_SUFFIX}\` file, then run init again.`,
280
+ });
281
+ }
282
+ if (stale.length === 0 && review.length === 0) {
283
+ const customized = present.filter((state) => state.status === "customized");
284
+ add({
285
+ id: "generated-current",
286
+ status: "pass",
287
+ message: `Generated Refino runtime files are the ${TOOL_VERSION} ones (${present.length} file(s)).`,
288
+ ...(customized.length > 0
289
+ ? { details: customized.map((state) => `${state.path} has local changes, and Refino's version of it has not changed since — nothing to upgrade.`) }
290
+ : {}),
291
+ });
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
+ }
329
+ }
192
330
  export function verifyIntegration(rootPath, options = {}) {
193
331
  const inspection = inspectRepository(rootPath, options);
194
332
  const appDir = appDirectory(inspection);
@@ -399,6 +537,8 @@ export function verifyIntegration(rootPath, options = {}) {
399
537
  add({ id: "env-example", status: "pass", message: ".env.example documents the Refino variables." });
400
538
  else
401
539
  add({ id: "env-example", status: "warn", message: ".env.example does not document EDITOR_*/COPY_* variables.", fix: "Run `refino init --agent` or add the block by hand." });
540
+ // Generated runtime files: current, refreshable, or the owner's now.
541
+ generatedChecks(appDir, inspection, contentFile, hosted, config?.refino?.installMethod, add);
402
542
  // Scripts (plus a tsc fallback when a TypeScript project has no typecheck script).
403
543
  const commands = [];
404
544
  for (const command of verificationCommands(inspection)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getrefino/onboarding",
3
- "version": "0.1.0-rc.2",
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.2"
41
+ "@getrefino/core": "0.1.0-rc.4"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/node": "22.20.2",