@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/README.md +11 -3
- package/dist/agent.d.ts +0 -4
- package/dist/agent.js +2 -1
- package/dist/apply.js +71 -34
- package/dist/generated.d.ts +30 -0
- package/dist/generated.js +168 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -0
- package/dist/inspect.d.ts +2 -0
- package/dist/inspect.js +14 -0
- package/dist/template-file.d.ts +38 -0
- package/dist/template-file.js +44 -0
- package/dist/templates-hosted.d.ts +2 -13
- package/dist/templates-hosted.js +65 -16
- package/dist/templates-legacy.d.ts +25 -0
- package/dist/templates-legacy.js +221 -0
- package/dist/templates.d.ts +2 -4
- package/dist/templates.js +32 -41
- package/dist/types.d.ts +75 -0
- package/dist/verify.js +143 -3
- package/package.json +2 -2
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 {
|
|
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.
|
|
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.
|
|
41
|
+
"@getrefino/core": "0.1.0-rc.4"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/node": "22.20.2",
|