@getrefino/cli 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.
- package/LICENSE +21 -0
- package/README.md +90 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +10 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +282 -0
- package/dist/format.d.ts +14 -0
- package/dist/format.js +147 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Refino contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# @getrefino/cli — the Refino installer
|
|
2
|
+
|
|
3
|
+
**Refino lets site owners edit visible website copy in the browser while the
|
|
4
|
+
repository stays the source of truth.**
|
|
5
|
+
|
|
6
|
+
This package is how Refino is installed into a website. If you were asked to
|
|
7
|
+
"install Refino", this is the package, and the command is:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @getrefino/cli init
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The installed binary is named `refino`. **Refino** is the product; `getrefino`
|
|
14
|
+
is only the npm namespace.
|
|
15
|
+
|
|
16
|
+
## What Refino is
|
|
17
|
+
|
|
18
|
+
The owner of a coded website opens their real site, clicks a headline or a
|
|
19
|
+
paragraph, rewrites it in place, and presses Save. The change is committed to
|
|
20
|
+
the website's own repository and the normal deployment pipeline ships it.
|
|
21
|
+
|
|
22
|
+
- The copy lives in **one JSON file in the repository** (`content/copy.json` by
|
|
23
|
+
default). That file is the only source of truth.
|
|
24
|
+
- An edit is a commit and a one-line diff. There is no CMS, no database and no
|
|
25
|
+
sync step.
|
|
26
|
+
- It is builder-independent: it does not matter whether Claude Code, Cursor,
|
|
27
|
+
Codex, Lovable, Replit or a human wrote the site.
|
|
28
|
+
- Removing Refino leaves the content exactly where it was.
|
|
29
|
+
|
|
30
|
+
Refino changes words. Your coding agent keeps doing features, layouts and
|
|
31
|
+
components.
|
|
32
|
+
|
|
33
|
+
## Install into a site
|
|
34
|
+
|
|
35
|
+
Run this in the website's repository:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx @getrefino/cli init --agent
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`init` inspects the repository (framework, router, routes, package manager,
|
|
42
|
+
auth, server, hosting, git), scans the pages for visible copy, proposes stable
|
|
43
|
+
ids, and writes:
|
|
44
|
+
|
|
45
|
+
- the canonical copy file, with every selected string already in it
|
|
46
|
+
- isolated setup files under `refino/`, plus `refino.config.json`
|
|
47
|
+
- `.refino/plan.json` — the machine-readable migration plan
|
|
48
|
+
- `.refino/AGENT_INSTRUCTIONS.md` — what a coding agent must do next
|
|
49
|
+
|
|
50
|
+
Then check the result:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx @getrefino/cli verify
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## If you are a coding agent
|
|
57
|
+
|
|
58
|
+
1. Run `npx @getrefino/cli init --agent` in the repository.
|
|
59
|
+
2. Read `.refino/AGENT_INSTRUCTIONS.md` and `.refino/plan.json` and do exactly
|
|
60
|
+
what they say. **They are authoritative.** They were generated from an
|
|
61
|
+
inspection of this specific repository — do not invent integration steps,
|
|
62
|
+
do not add a second content store, and do not widen the scope.
|
|
63
|
+
3. Run `npx @getrefino/cli verify` and fix only what it reports. Repeat until
|
|
64
|
+
it passes.
|
|
65
|
+
|
|
66
|
+
## Commands
|
|
67
|
+
|
|
68
|
+
| Command | What it does |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| `refino init` | Inspect, plan, and write setup files + agent instructions |
|
|
71
|
+
| `refino inspect` | Report the repository facts only |
|
|
72
|
+
| `refino scan` | List the visible copy it found, with reasons |
|
|
73
|
+
| `refino plan` | Print the migration plan (`--format text\|json\|agent`) |
|
|
74
|
+
| `refino verify` | Check an installation and say exactly what is missing |
|
|
75
|
+
|
|
76
|
+
Every command takes `--json` for machine-readable output and `--cwd <dir>` to
|
|
77
|
+
operate on another directory. `refino init --dry-run` shows what would be
|
|
78
|
+
written without writing anything; no command ever overwrites an existing file.
|
|
79
|
+
|
|
80
|
+
## Packages
|
|
81
|
+
|
|
82
|
+
`@getrefino/cli` is a thin command layer over
|
|
83
|
+
[`@getrefino/onboarding`](https://www.npmjs.com/package/@getrefino/onboarding).
|
|
84
|
+
A site that has been onboarded ships only the runtime packages:
|
|
85
|
+
|
|
86
|
+
- [`@getrefino/core`](https://www.npmjs.com/package/@getrefino/core) — content model and persistence contracts
|
|
87
|
+
- [`@getrefino/react`](https://www.npmjs.com/package/@getrefino/react) — `RefinoProvider` and `EditableText`
|
|
88
|
+
- [`@getrefino/github`](https://www.npmjs.com/package/@getrefino/github) — commits the copy file through the GitHub Contents API (self-hosted mode only)
|
|
89
|
+
|
|
90
|
+
<https://refino.dev>
|
package/dist/bin.d.ts
ADDED
package/dist/bin.js
ADDED
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function run(argv: readonly string[]): Promise<number>;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command layer over @getrefino/onboarding. Thin on purpose: every
|
|
3
|
+
* decision lives in the library so a hosted onboarding service can reuse it.
|
|
4
|
+
*/
|
|
5
|
+
import { writeFileSync } from "node:fs";
|
|
6
|
+
import { resolve } from "node:path";
|
|
7
|
+
import { createInterface } from "node:readline/promises";
|
|
8
|
+
import { parseArgs } from "node:util";
|
|
9
|
+
import { INSTRUCTIONS_FILE, PLAN_FILE, TOOL_VERSION, appDirectory, applyPlan, inspectRepository, planMigration, renderAgentInstructions, scanCopy, verifyIntegration, } from "@getrefino/onboarding";
|
|
10
|
+
import { bold, cyan, dim, formatApply, formatCandidates, formatInspection, formatPlanSummary, formatVerification, red, yellow } from "./format.js";
|
|
11
|
+
const TAGLINE = "Refino lets site owners edit visible website copy in the browser while the repository stays the source of truth.";
|
|
12
|
+
const HELP = `refino ${TOOL_VERSION} (@getrefino/cli)
|
|
13
|
+
|
|
14
|
+
${TAGLINE}
|
|
15
|
+
|
|
16
|
+
It is not a CMS: the copy lives in one JSON file in the repository, an edit is a
|
|
17
|
+
commit, and removing Refino leaves the content exactly where it was.
|
|
18
|
+
|
|
19
|
+
Install into a site
|
|
20
|
+
npx @getrefino/cli init --agent inspect the repo, write the setup files and
|
|
21
|
+
an authoritative plan + agent instructions
|
|
22
|
+
npx @getrefino/cli verify check the result; repeat until it passes
|
|
23
|
+
|
|
24
|
+
Usage
|
|
25
|
+
refino init [--refino-site <id> [--refino-url <origin>]] [--route <path>]... [--all] [--agent | --dry-run | --plan] [--yes]
|
|
26
|
+
refino inspect [--json]
|
|
27
|
+
refino scan [--route <path>]... [--all] [--no-shared] [--json] [--include-excluded]
|
|
28
|
+
refino plan [--route <path>]... [--all] [--format text|json|agent] [--out <file>]
|
|
29
|
+
refino verify [--run] [--json]
|
|
30
|
+
|
|
31
|
+
Options
|
|
32
|
+
--cwd <dir> Repository to operate on (default: current directory)
|
|
33
|
+
--app <dir> App directory inside a monorepo (auto-detected)
|
|
34
|
+
--route <path> Route to migrate; repeatable (default: "/")
|
|
35
|
+
--all Every discovered route
|
|
36
|
+
--no-shared Exclude shared chrome (layouts, nav, footer)
|
|
37
|
+
--content-file <p> Where the canonical copy file lives (default: content/copy.json)
|
|
38
|
+
--refino-site <id> Connect to Refino (hosted mode): the site id from the Refino dashboard.
|
|
39
|
+
The site then needs no editor password, secret, repository token or server code.
|
|
40
|
+
--refino-url <origin> Refino app origin (default: https://app.refino.dev)
|
|
41
|
+
--agent Write safe setup files + .refino/AGENT_INSTRUCTIONS.md
|
|
42
|
+
--dry-run Show what --agent would write without writing
|
|
43
|
+
--plan Print the plan only
|
|
44
|
+
--yes Do not prompt
|
|
45
|
+
--json Machine-readable output
|
|
46
|
+
--run verify: also run the repository's typecheck/lint/test/build
|
|
47
|
+
|
|
48
|
+
If you are a coding agent
|
|
49
|
+
1. npx @getrefino/cli init --agent (inspect, plan, write setup files + instructions)
|
|
50
|
+
2. Read .refino/AGENT_INSTRUCTIONS.md and .refino/plan.json and do exactly what they say.
|
|
51
|
+
They are authoritative: do not invent integration steps of your own.
|
|
52
|
+
3. npx @getrefino/cli verify (fix only what it reports, then run it again)
|
|
53
|
+
`;
|
|
54
|
+
function parse(argv) {
|
|
55
|
+
const { values, positionals } = parseArgs({
|
|
56
|
+
args: [...argv],
|
|
57
|
+
allowPositionals: true,
|
|
58
|
+
options: {
|
|
59
|
+
cwd: { type: "string" },
|
|
60
|
+
app: { type: "string" },
|
|
61
|
+
route: { type: "string", multiple: true },
|
|
62
|
+
all: { type: "boolean", default: false },
|
|
63
|
+
shared: { type: "boolean", default: true },
|
|
64
|
+
"no-shared": { type: "boolean", default: false },
|
|
65
|
+
"content-file": { type: "string" },
|
|
66
|
+
"refino-site": { type: "string" },
|
|
67
|
+
"refino-url": { type: "string" },
|
|
68
|
+
agent: { type: "boolean", default: false },
|
|
69
|
+
"dry-run": { type: "boolean", default: false },
|
|
70
|
+
plan: { type: "boolean", default: false },
|
|
71
|
+
yes: { type: "boolean", default: false, short: "y" },
|
|
72
|
+
json: { type: "boolean", default: false },
|
|
73
|
+
"include-excluded": { type: "boolean", default: false },
|
|
74
|
+
run: { type: "boolean", default: false },
|
|
75
|
+
format: { type: "string", default: "text" },
|
|
76
|
+
out: { type: "string" },
|
|
77
|
+
help: { type: "boolean", default: false, short: "h" },
|
|
78
|
+
version: { type: "boolean", default: false, short: "v" },
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
return {
|
|
82
|
+
command: positionals[0],
|
|
83
|
+
cwd: resolve(values.cwd ?? process.cwd()),
|
|
84
|
+
app: values.app,
|
|
85
|
+
routes: values.route,
|
|
86
|
+
all: values.all,
|
|
87
|
+
shared: !values["no-shared"],
|
|
88
|
+
contentFile: values["content-file"],
|
|
89
|
+
refinoSite: values["refino-site"],
|
|
90
|
+
refinoUrl: values["refino-url"],
|
|
91
|
+
agent: values.agent,
|
|
92
|
+
dryRun: values["dry-run"],
|
|
93
|
+
plan: values.plan,
|
|
94
|
+
yes: values.yes,
|
|
95
|
+
json: values.json,
|
|
96
|
+
includeExcluded: values["include-excluded"],
|
|
97
|
+
run: values.run,
|
|
98
|
+
format: values.format,
|
|
99
|
+
out: values.out,
|
|
100
|
+
help: values.help,
|
|
101
|
+
version: values.version,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function planOptions(args) {
|
|
105
|
+
const options = {};
|
|
106
|
+
if (args.routes)
|
|
107
|
+
options.routes = args.routes;
|
|
108
|
+
if (args.all)
|
|
109
|
+
options.all = true;
|
|
110
|
+
options.includeShared = args.shared;
|
|
111
|
+
if (args.contentFile)
|
|
112
|
+
options.contentFile = args.contentFile;
|
|
113
|
+
if (args.refinoSite)
|
|
114
|
+
options.refino = args.refinoUrl ? { siteId: args.refinoSite, appUrl: args.refinoUrl } : { siteId: args.refinoSite };
|
|
115
|
+
else if (args.refinoUrl)
|
|
116
|
+
throw new Error("--refino-url needs --refino-site.");
|
|
117
|
+
return options;
|
|
118
|
+
}
|
|
119
|
+
function inspect(args) {
|
|
120
|
+
return inspectRepository(args.cwd, args.app ? { app: args.app } : {});
|
|
121
|
+
}
|
|
122
|
+
function requireSupported(inspection) {
|
|
123
|
+
if (inspection.supported)
|
|
124
|
+
return true;
|
|
125
|
+
console.log(formatInspection(inspection));
|
|
126
|
+
console.log("");
|
|
127
|
+
console.log(red("This repository cannot be onboarded automatically. Refino v1 supports Next.js, Vite + React and plain React projects."));
|
|
128
|
+
console.log(dim("You can still integrate Refino by hand: https://refino.dev"));
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
async function prompt(question) {
|
|
132
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
133
|
+
try {
|
|
134
|
+
return (await rl.question(question)).trim();
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
rl.close();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function writePlanOutputs(args, plan) {
|
|
141
|
+
if (!args.out)
|
|
142
|
+
return;
|
|
143
|
+
const content = args.format === "json" ? `${JSON.stringify(plan, null, 2)}\n` : args.format === "agent" ? renderAgentInstructions(plan) : formatPlanSummary(plan);
|
|
144
|
+
writeFileSync(resolve(args.cwd, args.out), content, "utf8");
|
|
145
|
+
console.log(dim(`Wrote ${args.out}`));
|
|
146
|
+
}
|
|
147
|
+
export async function run(argv) {
|
|
148
|
+
let args;
|
|
149
|
+
try {
|
|
150
|
+
args = parse(argv);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
console.error(error.message);
|
|
154
|
+
console.error(HELP);
|
|
155
|
+
return 2;
|
|
156
|
+
}
|
|
157
|
+
if (args.version) {
|
|
158
|
+
console.log(TOOL_VERSION);
|
|
159
|
+
return 0;
|
|
160
|
+
}
|
|
161
|
+
if (args.help || !args.command) {
|
|
162
|
+
console.log(HELP);
|
|
163
|
+
return args.command ? 0 : 2;
|
|
164
|
+
}
|
|
165
|
+
switch (args.command) {
|
|
166
|
+
case "inspect": {
|
|
167
|
+
const inspection = inspect(args);
|
|
168
|
+
console.log(args.json ? JSON.stringify(inspection, null, 2) : formatInspection(inspection));
|
|
169
|
+
return inspection.supported ? 0 : 1;
|
|
170
|
+
}
|
|
171
|
+
case "scan": {
|
|
172
|
+
const inspection = inspect(args);
|
|
173
|
+
if (!requireSupported(inspection))
|
|
174
|
+
return 1;
|
|
175
|
+
const options = { includeShared: args.shared };
|
|
176
|
+
if (args.routes)
|
|
177
|
+
options.routes = args.routes;
|
|
178
|
+
if (args.all)
|
|
179
|
+
options.all = true;
|
|
180
|
+
const result = scanCopy(inspection, options);
|
|
181
|
+
if (args.json) {
|
|
182
|
+
console.log(JSON.stringify(result, null, 2));
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
console.log(formatInspection(inspection));
|
|
186
|
+
console.log("");
|
|
187
|
+
console.log(bold(`Scanned ${result.filesScanned.length} files, ${result.candidates.length} strings found`));
|
|
188
|
+
console.log(formatCandidates(result.candidates, { includeExcluded: args.includeExcluded }));
|
|
189
|
+
}
|
|
190
|
+
return 0;
|
|
191
|
+
}
|
|
192
|
+
case "plan": {
|
|
193
|
+
const inspection = inspect(args);
|
|
194
|
+
if (!requireSupported(inspection))
|
|
195
|
+
return 1;
|
|
196
|
+
const plan = planMigration(inspection, planOptions(args));
|
|
197
|
+
if (args.format === "json")
|
|
198
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
199
|
+
else if (args.format === "agent")
|
|
200
|
+
console.log(renderAgentInstructions(plan));
|
|
201
|
+
else {
|
|
202
|
+
console.log(formatInspection(inspection));
|
|
203
|
+
console.log("");
|
|
204
|
+
console.log(formatPlanSummary(plan));
|
|
205
|
+
console.log("");
|
|
206
|
+
console.log(formatCandidates(plan.copy, { includeExcluded: args.includeExcluded }));
|
|
207
|
+
}
|
|
208
|
+
writePlanOutputs(args, plan);
|
|
209
|
+
return 0;
|
|
210
|
+
}
|
|
211
|
+
case "init": {
|
|
212
|
+
console.log(bold(`Refino ${TOOL_VERSION}`));
|
|
213
|
+
console.log(TAGLINE);
|
|
214
|
+
console.log(dim("Copy lives in one JSON file in this repository. An edit is a commit. No CMS, no database, no sync."));
|
|
215
|
+
console.log("");
|
|
216
|
+
const inspection = inspect(args);
|
|
217
|
+
if (!requireSupported(inspection))
|
|
218
|
+
return 1;
|
|
219
|
+
const plan = planMigration(inspection, planOptions(args));
|
|
220
|
+
const appDir = appDirectory(inspection);
|
|
221
|
+
console.log(formatInspection(inspection));
|
|
222
|
+
console.log("");
|
|
223
|
+
console.log(formatPlanSummary(plan));
|
|
224
|
+
console.log("");
|
|
225
|
+
let mode;
|
|
226
|
+
if (args.agent)
|
|
227
|
+
mode = "agent";
|
|
228
|
+
else if (args.dryRun)
|
|
229
|
+
mode = "dry-run";
|
|
230
|
+
else if (args.plan)
|
|
231
|
+
mode = "plan";
|
|
232
|
+
else if (args.yes || !process.stdin.isTTY) {
|
|
233
|
+
mode = "dry-run";
|
|
234
|
+
console.log(dim("Non-interactive: showing a dry run. Pass --agent to write the setup files and agent instructions."));
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
console.log(bold("Choose an onboarding method:"));
|
|
238
|
+
console.log(" 1. Prepare for my coding agent (write safe setup files + AGENT_INSTRUCTIONS.md)");
|
|
239
|
+
console.log(" 2. Show what option 1 would write (dry run)");
|
|
240
|
+
console.log(" 3. Show the migration plan only");
|
|
241
|
+
const answer = await prompt("Enter 1, 2 or 3: ");
|
|
242
|
+
mode = answer === "1" ? "agent" : answer === "3" ? "plan" : "dry-run";
|
|
243
|
+
}
|
|
244
|
+
if (mode === "plan") {
|
|
245
|
+
console.log(formatCandidates(plan.copy));
|
|
246
|
+
return 0;
|
|
247
|
+
}
|
|
248
|
+
if (inspection.git.isRepository && inspection.git.clean === false && mode === "agent" && !args.yes) {
|
|
249
|
+
console.log(yellow("The working tree has uncommitted changes. Commit or stash first so the generated files are easy to review."));
|
|
250
|
+
const answer = await prompt("Continue anyway? (y/N) ");
|
|
251
|
+
if (answer.toLowerCase() !== "y")
|
|
252
|
+
return 1;
|
|
253
|
+
}
|
|
254
|
+
const result = applyPlan(appDir, plan, { dryRun: mode === "dry-run" });
|
|
255
|
+
console.log(formatApply(result));
|
|
256
|
+
console.log("");
|
|
257
|
+
if (mode === "dry-run") {
|
|
258
|
+
console.log(dim("Nothing was written. Run `refino init --agent` to write these files."));
|
|
259
|
+
return 0;
|
|
260
|
+
}
|
|
261
|
+
console.log(bold("Next steps"));
|
|
262
|
+
if (result.installCommand)
|
|
263
|
+
console.log(` 1. ${result.installCommand}`);
|
|
264
|
+
console.log(` ${result.installCommand ? "2" : "1"}. Give your coding agent this instruction:`);
|
|
265
|
+
console.log(cyan(` "Read ${INSTRUCTIONS_FILE} and ${PLAN_FILE} in this repository and do exactly what they say. Then run npx @getrefino/cli verify and fix only what it reports."`));
|
|
266
|
+
console.log(` ${result.installCommand ? "3" : "2"}. npx @getrefino/cli verify`);
|
|
267
|
+
return 0;
|
|
268
|
+
}
|
|
269
|
+
case "verify": {
|
|
270
|
+
const options = { run: args.run };
|
|
271
|
+
if (args.app)
|
|
272
|
+
options.app = args.app;
|
|
273
|
+
const result = verifyIntegration(args.cwd, options);
|
|
274
|
+
console.log(args.json ? JSON.stringify(result, null, 2) : formatVerification(result));
|
|
275
|
+
return result.ok ? 0 : 1;
|
|
276
|
+
}
|
|
277
|
+
default:
|
|
278
|
+
console.error(`Unknown command "${args.command}".`);
|
|
279
|
+
console.error(HELP);
|
|
280
|
+
return 2;
|
|
281
|
+
}
|
|
282
|
+
}
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ApplyResult, CopyCandidate, MigrationPlan, RepositoryInspection, VerificationResult } from "@getrefino/onboarding";
|
|
2
|
+
export declare const bold: (text: string) => string;
|
|
3
|
+
export declare const dim: (text: string) => string;
|
|
4
|
+
export declare const green: (text: string) => string;
|
|
5
|
+
export declare const yellow: (text: string) => string;
|
|
6
|
+
export declare const red: (text: string) => string;
|
|
7
|
+
export declare const cyan: (text: string) => string;
|
|
8
|
+
export declare function formatInspection(inspection: RepositoryInspection): string;
|
|
9
|
+
export declare function formatCandidates(candidates: readonly CopyCandidate[], options?: {
|
|
10
|
+
includeExcluded?: boolean;
|
|
11
|
+
}): string;
|
|
12
|
+
export declare function formatPlanSummary(plan: MigrationPlan): string;
|
|
13
|
+
export declare function formatApply(result: ApplyResult): string;
|
|
14
|
+
export declare function formatVerification(result: VerificationResult): string;
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
2
|
+
const paint = (code) => (text) => (useColor ? `[${code}m${text}[0m` : text);
|
|
3
|
+
export const bold = paint("1");
|
|
4
|
+
export const dim = paint("2");
|
|
5
|
+
export const green = paint("32");
|
|
6
|
+
export const yellow = paint("33");
|
|
7
|
+
export const red = paint("31");
|
|
8
|
+
export const cyan = paint("36");
|
|
9
|
+
function line(label, value) {
|
|
10
|
+
return `${dim(label.padEnd(26))}${value}`;
|
|
11
|
+
}
|
|
12
|
+
export function formatInspection(inspection) {
|
|
13
|
+
const rows = [];
|
|
14
|
+
rows.push(bold("Repository"));
|
|
15
|
+
rows.push(line("Root", inspection.root));
|
|
16
|
+
rows.push(line("App root", inspection.appRoot + (inspection.monorepo.detected ? ` (monorepo: ${inspection.monorepo.tool ?? "workspaces"}, ${inspection.monorepo.apps.length} packages)` : "")));
|
|
17
|
+
rows.push(line("Package manager", inspection.packageManager));
|
|
18
|
+
rows.push(line("Framework", inspection.framework === "unsupported" ? red("unsupported") : `${inspection.framework}${inspection.next ? ` ${inspection.next.version}` : inspection.vite ? ` (vite ${inspection.vite.version})` : ""}`));
|
|
19
|
+
rows.push(line("Router", inspection.router));
|
|
20
|
+
rows.push(line("Language", inspection.language));
|
|
21
|
+
rows.push(line("React", inspection.react ? inspection.react.version : "no"));
|
|
22
|
+
rows.push(line("Routes found", `${inspection.routes.length}${inspection.routes.length > 0 ? ` (${inspection.routes.slice(0, 6).map((route) => route.path).join(", ")}${inspection.routes.length > 6 ? ", …" : ""})` : ""}`));
|
|
23
|
+
rows.push(line("Layouts", inspection.layouts.join(", ") || "none"));
|
|
24
|
+
rows.push(line("Scripts", Object.entries(inspection.scripts).map(([key, value]) => `${key}=${JSON.stringify(value)}`).join(" ") || "none"));
|
|
25
|
+
rows.push(line("Content dirs", inspection.contentDirs.join(", ") || "none"));
|
|
26
|
+
rows.push(line("Auth", inspection.auth.libraries.join(", ") || "none detected"));
|
|
27
|
+
rows.push(line("Server", `${inspection.server.kind}${inspection.server.apiDir ? ` (${inspection.server.apiDir})` : ""}`));
|
|
28
|
+
rows.push(line("Hosting", inspection.hosting.provider ?? "not identified"));
|
|
29
|
+
rows.push(line("Git", inspection.git.isRepository ? `${inspection.git.branch ?? "?"}${inspection.git.clean === false ? " (uncommitted changes)" : ""}${inspection.git.github ? ` github:${inspection.git.github.owner}/${inspection.git.github.repo}` : ""}` : "not a repository"));
|
|
30
|
+
const ic = inspection.refino;
|
|
31
|
+
rows.push(line("Refino", ic.packages.length === 0 && !ic.contentFile ? "none" : `${ic.packages.map((p) => p.name).join(", ") || "no packages"}; content: ${ic.contentFile ?? "none"}; provider: ${ic.providerFiles.length > 0 ? "yes" : "no"}; endpoint: ${ic.endpointFiles.length > 0 ? "yes" : "no"}`));
|
|
32
|
+
if (!inspection.supported) {
|
|
33
|
+
rows.push("");
|
|
34
|
+
rows.push(red(bold("Automatic onboarding is unavailable:")));
|
|
35
|
+
for (const reason of inspection.unsupportedReasons)
|
|
36
|
+
rows.push(` - ${reason}`);
|
|
37
|
+
}
|
|
38
|
+
return rows.join("\n");
|
|
39
|
+
}
|
|
40
|
+
function statusColor(status) {
|
|
41
|
+
switch (status) {
|
|
42
|
+
case "selected":
|
|
43
|
+
return green;
|
|
44
|
+
case "needs-review":
|
|
45
|
+
return yellow;
|
|
46
|
+
case "unsupported":
|
|
47
|
+
return red;
|
|
48
|
+
default:
|
|
49
|
+
return dim;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function formatCandidates(candidates, options = {}) {
|
|
53
|
+
const rows = [];
|
|
54
|
+
const shown = candidates.filter((candidate) => options.includeExcluded || candidate.status !== "excluded");
|
|
55
|
+
let currentFile = "";
|
|
56
|
+
for (const candidate of shown) {
|
|
57
|
+
if (candidate.file !== currentFile) {
|
|
58
|
+
currentFile = candidate.file;
|
|
59
|
+
rows.push("");
|
|
60
|
+
rows.push(bold(currentFile) + dim(` (${candidate.scope})`));
|
|
61
|
+
}
|
|
62
|
+
const status = statusColor(candidate.status)(candidate.status.padEnd(12));
|
|
63
|
+
const id = candidate.id ?? "(no id)";
|
|
64
|
+
const text = candidate.text.length > 56 ? `${candidate.text.slice(0, 55)}…` : candidate.text;
|
|
65
|
+
rows.push(` ${status} ${cyan(id.padEnd(40))} ${dim(`:${candidate.line}`.padEnd(6))} ${JSON.stringify(text)}`);
|
|
66
|
+
if (candidate.status !== "selected")
|
|
67
|
+
rows.push(` ${" ".repeat(12)} ${dim(candidate.reason)}`);
|
|
68
|
+
}
|
|
69
|
+
return rows.join("\n");
|
|
70
|
+
}
|
|
71
|
+
export function formatPlanSummary(plan) {
|
|
72
|
+
const rows = [];
|
|
73
|
+
rows.push(bold("Migration plan"));
|
|
74
|
+
rows.push(line("Scope", plan.scope.description));
|
|
75
|
+
rows.push(line("Content file", `${plan.contentFile.path}${plan.contentFile.exists ? " (exists, will merge)" : " (new)"}`));
|
|
76
|
+
rows.push(line("Provider", `${plan.integration.provider.file ?? "?"} [${plan.integration.provider.strategy}]`));
|
|
77
|
+
rows.push(line("Endpoint", `${plan.integration.endpoint.path} [${plan.integration.endpoint.kind}]`));
|
|
78
|
+
rows.push(line("Auth", `${plan.integration.auth.strategy}${plan.integration.auth.existing.length > 0 ? ` (existing: ${plan.integration.auth.existing.join(", ")})` : ""}`));
|
|
79
|
+
rows.push(line("Persistence", `local file in development; GitHub via ${plan.integration.persistence.github.envVars.join(", ")}`));
|
|
80
|
+
rows.push("");
|
|
81
|
+
rows.push(bold("Copy"));
|
|
82
|
+
rows.push(line("Selected", green(String(plan.summary.selected))));
|
|
83
|
+
rows.push(line("Needs review", yellow(String(plan.summary.needsReview))));
|
|
84
|
+
rows.push(line("Unsupported", red(String(plan.summary.unsupported))));
|
|
85
|
+
rows.push(line("Excluded (not copy)", dim(String(plan.summary.excluded))));
|
|
86
|
+
const scopes = Object.entries(plan.summary.byScope).sort(([a], [b]) => (a === "shared" ? 1 : b === "shared" ? -1 : a.localeCompare(b)));
|
|
87
|
+
if (scopes.length > 0) {
|
|
88
|
+
rows.push("");
|
|
89
|
+
rows.push(bold("Recommended first migration"));
|
|
90
|
+
for (const [scope, count] of scopes)
|
|
91
|
+
rows.push(` - ${scope === "shared" ? "shared chrome (nav, footer, layouts)" : scope === "/" ? "homepage (/)" : scope}: ${count} strings`);
|
|
92
|
+
}
|
|
93
|
+
rows.push("");
|
|
94
|
+
rows.push(bold("Files"));
|
|
95
|
+
for (const file of plan.files)
|
|
96
|
+
rows.push(` ${dim(file.action.padEnd(7))} ${file.path} ${dim(file.description)}`);
|
|
97
|
+
if (plan.verification.commands.length > 0) {
|
|
98
|
+
rows.push("");
|
|
99
|
+
rows.push(bold("Verification"));
|
|
100
|
+
for (const command of plan.verification.commands)
|
|
101
|
+
rows.push(` ${command}`);
|
|
102
|
+
rows.push(` ${plan.verification.cli}`);
|
|
103
|
+
}
|
|
104
|
+
if (plan.unresolved.length > 0) {
|
|
105
|
+
rows.push("");
|
|
106
|
+
rows.push(bold(yellow("Needs a human or agent")));
|
|
107
|
+
for (const item of plan.unresolved)
|
|
108
|
+
rows.push(` - ${item}`);
|
|
109
|
+
}
|
|
110
|
+
return rows.join("\n");
|
|
111
|
+
}
|
|
112
|
+
export function formatApply(result) {
|
|
113
|
+
const rows = [];
|
|
114
|
+
rows.push(bold(result.dryRun ? "Dry run: files that would be written" : "Files written"));
|
|
115
|
+
for (const file of result.written)
|
|
116
|
+
rows.push(` ${green(file.action.padEnd(7))} ${file.path} ${dim(file.description)}`);
|
|
117
|
+
if (result.skipped.length > 0) {
|
|
118
|
+
rows.push(bold("Skipped"));
|
|
119
|
+
for (const item of result.skipped)
|
|
120
|
+
rows.push(` ${dim("skip".padEnd(7))} ${item.path} ${dim(item.reason)}`);
|
|
121
|
+
}
|
|
122
|
+
return rows.join("\n");
|
|
123
|
+
}
|
|
124
|
+
export function formatVerification(result) {
|
|
125
|
+
const rows = [];
|
|
126
|
+
const mark = { pass: green("✔"), fail: red("✖"), warn: yellow("▲"), skip: dim("-") };
|
|
127
|
+
for (const check of result.checks) {
|
|
128
|
+
rows.push(`${mark[check.status]} ${check.message}`);
|
|
129
|
+
for (const detail of check.details ?? [])
|
|
130
|
+
rows.push(` ${dim(detail)}`);
|
|
131
|
+
if (check.fix && check.status !== "pass")
|
|
132
|
+
rows.push(` ${dim("fix:")} ${check.fix}`);
|
|
133
|
+
}
|
|
134
|
+
if (result.commands.length > 0) {
|
|
135
|
+
rows.push("");
|
|
136
|
+
rows.push(bold("Repository checks"));
|
|
137
|
+
for (const command of result.commands) {
|
|
138
|
+
if (!command.ran)
|
|
139
|
+
rows.push(`${dim("-")} ${command.command} ${dim("(not run; pass --run)")}`);
|
|
140
|
+
else
|
|
141
|
+
rows.push(`${command.ok ? green("✔") : red("✖")} ${command.command}${command.ok ? "" : `\n${dim(command.output ?? "")}`}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
rows.push("");
|
|
145
|
+
rows.push(result.ok ? green(bold("Verification passed.")) : red(bold("Verification found problems. Fix only what is listed above and run again.")));
|
|
146
|
+
return rows.join("\n");
|
|
147
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@getrefino/cli",
|
|
3
|
+
"version": "0.1.0-rc.1",
|
|
4
|
+
"description": "Refino installer. Run `npx @getrefino/cli init` in a website's repository to let its owner edit visible copy in the browser while Git remains the source of truth. Inspects the repo, writes an authoritative migration plan and instructions for a coding agent, then verifies the result.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"refino",
|
|
7
|
+
"install",
|
|
8
|
+
"installer",
|
|
9
|
+
"cli",
|
|
10
|
+
"copy",
|
|
11
|
+
"content",
|
|
12
|
+
"visual-editing",
|
|
13
|
+
"inline-editing",
|
|
14
|
+
"cms-alternative",
|
|
15
|
+
"react",
|
|
16
|
+
"nextjs",
|
|
17
|
+
"agent"
|
|
18
|
+
],
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"homepage": "https://refino.dev",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/forant/inline-copy.git",
|
|
24
|
+
"directory": "packages/cli"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public",
|
|
28
|
+
"provenance": false
|
|
29
|
+
},
|
|
30
|
+
"type": "module",
|
|
31
|
+
"bin": {
|
|
32
|
+
"refino": "./dist/bin.js"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist/**/*.js",
|
|
36
|
+
"dist/**/*.d.ts",
|
|
37
|
+
"README.md",
|
|
38
|
+
"LICENSE"
|
|
39
|
+
],
|
|
40
|
+
"exports": {
|
|
41
|
+
".": {
|
|
42
|
+
"types": "./dist/cli.d.ts",
|
|
43
|
+
"default": "./dist/cli.js"
|
|
44
|
+
},
|
|
45
|
+
"./package.json": "./package.json"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@getrefino/onboarding": "0.1.0-rc.1"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "22.20.2",
|
|
52
|
+
"vitest": "5.0.0"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json && chmod +x dist/bin.js",
|
|
56
|
+
"dev": "tsc -p tsconfig.build.json --watch --preserveWatchOutput",
|
|
57
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"clean": "rm -rf dist"
|
|
60
|
+
}
|
|
61
|
+
}
|