@bitvea/feedback-cli 1.0.0
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 +38 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +24 -0
- package/dist/bin.js.map +1 -0
- package/dist/commands/init.d.ts +97 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +273 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/register.d.ts +84 -0
- package/dist/commands/register.d.ts.map +1 -0
- package/dist/commands/register.js +125 -0
- package/dist/commands/register.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/install/env.d.ts +2 -0
- package/dist/install/env.d.ts.map +1 -0
- package/dist/install/env.js +19 -0
- package/dist/install/env.js.map +1 -0
- package/dist/install/fs.d.ts +16 -0
- package/dist/install/fs.d.ts.map +1 -0
- package/dist/install/fs.js +33 -0
- package/dist/install/fs.js.map +1 -0
- package/dist/install/strategies.d.ts +109 -0
- package/dist/install/strategies.d.ts.map +1 -0
- package/dist/install/strategies.js +208 -0
- package/dist/install/strategies.js.map +1 -0
- package/dist/registration.d.ts +43 -0
- package/dist/registration.d.ts.map +1 -0
- package/dist/registration.js +84 -0
- package/dist/registration.js.map +1 -0
- package/dist/run.d.ts +14 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +293 -0
- package/dist/run.js.map +1 -0
- package/dist/toolbarVersion.d.ts +36 -0
- package/dist/toolbarVersion.d.ts.map +1 -0
- package/dist/toolbarVersion.js +106 -0
- package/dist/toolbarVersion.js.map +1 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# @bitvea/feedback-cli
|
|
2
|
+
|
|
3
|
+
The installer for [BitVea Feedback](https://github.com/bitvea/feedback-toolbar): it installs the toolbar into an app, and it registers a deployment as a project.
|
|
4
|
+
|
|
5
|
+
You are not expected to type this package's name.
|
|
6
|
+
The command everybody uses is the toolbar's:
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npx @bitvea/feedback-toolbar init --api-url https://feedback.bitvea.com
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
That package depends on this one and hands it the argv, in the same process, so the command behaves exactly as it did when the installer lived inside it - and so the `prebuild` line it writes keeps working in repositories nobody here can edit.
|
|
13
|
+
|
|
14
|
+
## Commands
|
|
15
|
+
|
|
16
|
+
`init` installs the toolbar into the app in the current directory: the dependency, the backend URL, `<FeedbackToolbar />` in the root layout, and a `prebuild` script that registers the deployment on every build.
|
|
17
|
+
Re-running it changes nothing, and a run that cannot find a root layout changes nothing either.
|
|
18
|
+
|
|
19
|
+
`register` makes sure a deployment exists as a project in the dashboard.
|
|
20
|
+
It is what the `prebuild` line runs, and it is a command of its own because a script-tag install has no build to hook.
|
|
21
|
+
`--optional` reports a failure and exits 0, so a registration problem can never fail somebody's deploy.
|
|
22
|
+
|
|
23
|
+
`help` and `--version` print usage and the versions in play.
|
|
24
|
+
|
|
25
|
+
## Which toolbar version does `init` pin?
|
|
26
|
+
|
|
27
|
+
Whichever one it was launched through.
|
|
28
|
+
`npx @bitvea/feedback-toolbar init` writes a caret on exactly the toolbar npx just downloaded.
|
|
29
|
+
`--toolbar-version <range>` overrides it, `FEEDBACK_TOOLBAR_VERSION` does the same from the environment, and running this package directly falls back to the latest published version.
|
|
30
|
+
|
|
31
|
+
If none of those answers, `init` refuses and says so before touching your repository.
|
|
32
|
+
It will not guess a range: the one it writes ends up committed in your `package.json`.
|
|
33
|
+
|
|
34
|
+
## Dependencies
|
|
35
|
+
|
|
36
|
+
None, permanently.
|
|
37
|
+
This package is installed alongside the toolbar into every app that installs the toolbar, so its dependency tree is somebody else's dependency tree.
|
|
38
|
+
Its publishable check fails if `dependencies` is ever non-empty.
|
package/dist/bin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// `npx @bitvea/feedback-cli` - three lines, on purpose.
|
|
3
|
+
//
|
|
4
|
+
// Everything is in ./run.ts so that the toolbar's own bin can call the same
|
|
5
|
+
// function in the same process rather than spawning this file. Two entry
|
|
6
|
+
// points into one `run` is what makes "same argv, same stdout, same exit
|
|
7
|
+
// codes" true by construction instead of by care.
|
|
8
|
+
//
|
|
9
|
+
// The bin is named `feedback-cli` and never `feedback-toolbar`: both packages
|
|
10
|
+
// can end up in one `node_modules/.bin`, and two packages declaring one bin
|
|
11
|
+
// name is a last-writer-wins symlink that npm resolves silently. The loser
|
|
12
|
+
// would be the name every committed `prebuild` line invokes.
|
|
13
|
+
//
|
|
14
|
+
// The rejection handler is the one from the file this replaced, kept rather
|
|
15
|
+
// than swapped for a top-level `await`: an unexpected throw prints the error
|
|
16
|
+
// and exits 1, instead of Node's unhandled-rejection crash.
|
|
17
|
+
import { run } from "./run.js";
|
|
18
|
+
run(process.argv.slice(2)).then((code) => {
|
|
19
|
+
process.exitCode = code;
|
|
20
|
+
}, (err) => {
|
|
21
|
+
console.error(err);
|
|
22
|
+
process.exitCode = 1;
|
|
23
|
+
});
|
|
24
|
+
//# sourceMappingURL=bin.js.map
|
package/dist/bin.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,wDAAwD;AACxD,EAAE;AACF,4EAA4E;AAC5E,yEAAyE;AACzE,yEAAyE;AACzE,kDAAkD;AAClD,EAAE;AACF,8EAA8E;AAC9E,4EAA4E;AAC5E,2EAA2E;AAC3E,6DAA6D;AAC7D,EAAE;AACF,4EAA4E;AAC5E,6EAA6E;AAC7E,4DAA4D;AAC5D,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAE5B,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAC7B,CAAC,IAAI,EAAE,EAAE;IACP,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;AAC1B,CAAC,EACD,CAAC,GAAY,EAAE,EAAE;IACf,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CACF,CAAC"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { API_URL_ENV_VAR } from "../install/env.js";
|
|
2
|
+
import { type InstallerFs } from "../install/fs.js";
|
|
3
|
+
export { API_URL_ENV_VAR };
|
|
4
|
+
import { type Delivery, type InjectionStrategy } from "../install/strategies.js";
|
|
5
|
+
import type { RegisterProjectTransport } from "../registration.js";
|
|
6
|
+
/**
|
|
7
|
+
* The command wired into the host app's build, and the marker that says it is
|
|
8
|
+
* already wired. `--optional` is load-bearing: see the comment on
|
|
9
|
+
* `wirePrebuild` for why a registration failure must not fail a deploy.
|
|
10
|
+
*/
|
|
11
|
+
export declare const REGISTER_COMMAND = "feedback-toolbar register";
|
|
12
|
+
/**
|
|
13
|
+
* The prebuild line, with the backend URL BAKED IN.
|
|
14
|
+
*
|
|
15
|
+
* This is the second half of #11's reopened acceptance criterion, and the
|
|
16
|
+
* reason it reopened twice. `register` resolves its backend URL from a flag or
|
|
17
|
+
* from the build environment - and the build environment does not have it:
|
|
18
|
+
* `init` writes `NEXT_PUBLIC_FEEDBACK_TOOLBAR_API_URL` into `.env.local`,
|
|
19
|
+
* which create-next-app gitignores, and a plain node process does not load a
|
|
20
|
+
* dotenv file anyway. So on a real Vercel build the prebuild step threw "no
|
|
21
|
+
* backend URL", `--optional` turned that into one warning line, and no project
|
|
22
|
+
* ever appeared.
|
|
23
|
+
*
|
|
24
|
+
* package.json IS committed, so putting the URL here is what carries it into
|
|
25
|
+
* the build. It is a public URL, not a credential; the key still comes from
|
|
26
|
+
* `FEEDBACK_TOOLBAR_API_KEY` in the build environment and from nowhere else.
|
|
27
|
+
*/
|
|
28
|
+
export declare function prebuildScript(apiUrl: string): string;
|
|
29
|
+
export interface InitOptions {
|
|
30
|
+
/** Repository root to install into. */
|
|
31
|
+
root: string;
|
|
32
|
+
/** Base URL of the BitVea Feedback backend. */
|
|
33
|
+
apiUrl: string;
|
|
34
|
+
/**
|
|
35
|
+
* Deploy key authorising registration. Optional: without it the install
|
|
36
|
+
* still happens, and only the registration step is skipped - the prebuild
|
|
37
|
+
* step this writes registers on the next build instead.
|
|
38
|
+
*/
|
|
39
|
+
apiKey?: string;
|
|
40
|
+
/** Deployment URL to register the project under, when it is known. */
|
|
41
|
+
projectUrl?: string;
|
|
42
|
+
/**
|
|
43
|
+
* How to find out which range to write into the host app's `dependencies`
|
|
44
|
+
* for `@bitvea/feedback-toolbar`.
|
|
45
|
+
*
|
|
46
|
+
* It used to be a literal in this file, rewritten inside the toolbar's own
|
|
47
|
+
* `scripts/release.mjs` at publish time. After the split that rewrite would
|
|
48
|
+
* have had to reach across a package boundary, and any CLI published
|
|
49
|
+
* between two toolbar releases would have carried a number it had no way to
|
|
50
|
+
* know was stale. So the range is asked for, at the moment it is needed,
|
|
51
|
+
* and there is deliberately no fallback constant: a wrong range silently
|
|
52
|
+
* committed into somebody else's repository under their name is worse than
|
|
53
|
+
* a refusal naming a flag. See `resolveToolbarRange`.
|
|
54
|
+
*
|
|
55
|
+
* A function rather than a value because only the npm route writes a
|
|
56
|
+
* dependency at all. A script-tag install has no `package.json` entry to
|
|
57
|
+
* make, and refusing one for want of a number it will never use - on a
|
|
58
|
+
* machine that may have no registry to ask - would be a refusal about
|
|
59
|
+
* nothing.
|
|
60
|
+
*/
|
|
61
|
+
resolveToolbarRange?: () => string | Promise<string>;
|
|
62
|
+
/**
|
|
63
|
+
* What a script-tag install declares as its environment. Ignored by the npm
|
|
64
|
+
* route, which resolves it from the host build's own provider variables -
|
|
65
|
+
* see buildEnv.ts, and the neutral `NEXT_PUBLIC_FEEDBACK_TOOLBAR_ENV` for a
|
|
66
|
+
* host that has no entry there.
|
|
67
|
+
*/
|
|
68
|
+
environment?: string;
|
|
69
|
+
/** Injected so tests do not have to reach the network. */
|
|
70
|
+
registerProject?: RegisterProjectTransport;
|
|
71
|
+
/** Env bag, for the Vercel git metadata. Defaults to `process.env`. */
|
|
72
|
+
env?: Record<string, string | undefined>;
|
|
73
|
+
strategies?: readonly InjectionStrategy[];
|
|
74
|
+
}
|
|
75
|
+
export interface InitResult {
|
|
76
|
+
/** What actually changed, one line each, for the CLI to print. */
|
|
77
|
+
changes: string[];
|
|
78
|
+
/**
|
|
79
|
+
* Things worth saying that are not changes: a project that was already
|
|
80
|
+
* registered, or a registration that did not go through. These are kept
|
|
81
|
+
* apart from `changes` because `alreadyInstalled` is derived from that
|
|
82
|
+
* list, and a re-run must still report itself as a no-op even though it
|
|
83
|
+
* talks to the backend every time.
|
|
84
|
+
*/
|
|
85
|
+
notes: string[];
|
|
86
|
+
/** True when nothing needed doing, which is what a re-run should report. */
|
|
87
|
+
alreadyInstalled: boolean;
|
|
88
|
+
strategy: string;
|
|
89
|
+
/**
|
|
90
|
+
* How the host app gets the code. Reported because it decides what the
|
|
91
|
+
* developer has to do next: an npm install needs a dependency install and a
|
|
92
|
+
* redeploy, a script tag needs neither.
|
|
93
|
+
*/
|
|
94
|
+
delivery: Delivery;
|
|
95
|
+
}
|
|
96
|
+
export declare function init(fs: InstallerFs, options: InitOptions): Promise<InitResult>;
|
|
97
|
+
//# sourceMappingURL=init.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAkCA,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAY,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,CAAC;AAE3B,OAAO,EAML,KAAK,QAAQ,EACb,KAAK,iBAAiB,EACvB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAIhE;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,8BAA8B,CAAC;AAE5D;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,MAAM,WAAW,WAAW;IAC1B,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;;;;;;;;;;;;;OAkBG;IACH,mBAAmB,CAAC,EAAE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0DAA0D;IAC1D,eAAe,CAAC,EAAE,wBAAwB,CAAC;IAC3C,uEAAuE;IACvE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC,UAAU,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;CAC3C;AAED,MAAM,WAAW,UAAU;IACzB,kEAAkE;IAClE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,4EAA4E;IAC5E,gBAAgB,EAAE,OAAO,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED,wBAAsB,IAAI,CACxB,EAAE,EAAE,WAAW,EACf,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,CAAC,CAsLrB"}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
// `npx @bitvea/feedback-toolbar init` - the whole codemod, as one pure
|
|
2
|
+
// function over an InstallerFs so it can be driven by tests against a tree
|
|
3
|
+
// they control.
|
|
4
|
+
//
|
|
5
|
+
// Two properties matter more than anything else here, because this edits
|
|
6
|
+
// somebody else's repository:
|
|
7
|
+
//
|
|
8
|
+
// 1. Re-running changes nothing. Every step detects its own prior work
|
|
9
|
+
// rather than blindly appending, so the second run reports "already
|
|
10
|
+
// installed" and writes no file.
|
|
11
|
+
// 2. It never leaves a half-install. Everything that can fail is computed
|
|
12
|
+
// before anything is written, so a missing root layout ends the run with
|
|
13
|
+
// an untouched repository and a message saying what to do by hand.
|
|
14
|
+
//
|
|
15
|
+
// What changed after #11 was reopened: this command no longer pretends to be
|
|
16
|
+
// where registration happens. `init` runs on a laptop, where VERCEL_URL does
|
|
17
|
+
// not exist, so it had nothing to register and said nothing about it - a real
|
|
18
|
+
// install registered silently nothing. It now writes a `prebuild` script that
|
|
19
|
+
// runs `feedback-toolbar register`, so registration happens in the build,
|
|
20
|
+
// where those variables do exist. Registering here too is a convenience for
|
|
21
|
+
// the developer who passed both flags, not the mechanism.
|
|
22
|
+
//
|
|
23
|
+
// And what changed after it reopened a second time: NOTHING THIS COMMAND
|
|
24
|
+
// WRITES MAY DEPEND ON A GITIGNORED FILE. The install used to put the backend
|
|
25
|
+
// URL in `.env.local` alone, which create-next-app gitignores - so the
|
|
26
|
+
// prebuild step reached the build with no URL and skipped itself (one warning
|
|
27
|
+
// line, exit 0, because `--optional` must never fail a deploy), and the
|
|
28
|
+
// deployed toolbar had no URL either and rendered nothing. Two silent
|
|
29
|
+
// failures from one gitignored file. The URL is a public value, so it now
|
|
30
|
+
// goes where it survives a commit: into the injected component, and into the
|
|
31
|
+
// prebuild command in package.json. The only thing left for a developer to
|
|
32
|
+
// set by hand is the deploy KEY, which is a secret and belongs in the build
|
|
33
|
+
// environment - and the notes below say so explicitly.
|
|
34
|
+
import { API_URL_ENV_VAR } from "../install/env.js";
|
|
35
|
+
import { joinPath } from "../install/fs.js";
|
|
36
|
+
export { API_URL_ENV_VAR };
|
|
37
|
+
import { DEFAULT_SCRIPT_ENVIRONMENT, InjectionError, PACKAGE_NAME, SCRIPT_BUNDLE_PATH, STRATEGIES, } from "../install/strategies.js";
|
|
38
|
+
import { API_KEY_ENV_VAR, inferProjectUrl, register } from "./register.js";
|
|
39
|
+
const ENV_FILES = [".env.local", ".env"];
|
|
40
|
+
/**
|
|
41
|
+
* The command wired into the host app's build, and the marker that says it is
|
|
42
|
+
* already wired. `--optional` is load-bearing: see the comment on
|
|
43
|
+
* `wirePrebuild` for why a registration failure must not fail a deploy.
|
|
44
|
+
*/
|
|
45
|
+
export const REGISTER_COMMAND = "feedback-toolbar register";
|
|
46
|
+
/**
|
|
47
|
+
* The prebuild line, with the backend URL BAKED IN.
|
|
48
|
+
*
|
|
49
|
+
* This is the second half of #11's reopened acceptance criterion, and the
|
|
50
|
+
* reason it reopened twice. `register` resolves its backend URL from a flag or
|
|
51
|
+
* from the build environment - and the build environment does not have it:
|
|
52
|
+
* `init` writes `NEXT_PUBLIC_FEEDBACK_TOOLBAR_API_URL` into `.env.local`,
|
|
53
|
+
* which create-next-app gitignores, and a plain node process does not load a
|
|
54
|
+
* dotenv file anyway. So on a real Vercel build the prebuild step threw "no
|
|
55
|
+
* backend URL", `--optional` turned that into one warning line, and no project
|
|
56
|
+
* ever appeared.
|
|
57
|
+
*
|
|
58
|
+
* package.json IS committed, so putting the URL here is what carries it into
|
|
59
|
+
* the build. It is a public URL, not a credential; the key still comes from
|
|
60
|
+
* `FEEDBACK_TOOLBAR_API_KEY` in the build environment and from nowhere else.
|
|
61
|
+
*/
|
|
62
|
+
export function prebuildScript(apiUrl) {
|
|
63
|
+
return `${REGISTER_COMMAND} --optional --api-url ${apiUrl}`;
|
|
64
|
+
}
|
|
65
|
+
export async function init(fs, options) {
|
|
66
|
+
const { root, apiUrl } = options;
|
|
67
|
+
const env = options.env ?? {};
|
|
68
|
+
const strategies = options.strategies ?? STRATEGIES;
|
|
69
|
+
const packageJsonPath = joinPath(root, "package.json");
|
|
70
|
+
if (!fs.exists(packageJsonPath)) {
|
|
71
|
+
throw new InjectionError(`no package.json at ${root}. Run this from the root of the app you want to install into.`);
|
|
72
|
+
}
|
|
73
|
+
const pkg = JSON.parse(fs.readFile(packageJsonPath));
|
|
74
|
+
// Resolved before the injection, because the script-tag strategy writes it
|
|
75
|
+
// into the markup and refuses to install without it.
|
|
76
|
+
const projectUrl = options.projectUrl ?? inferProjectUrl(env);
|
|
77
|
+
// Find the injection site FIRST. It is the only step that can fail for a
|
|
78
|
+
// reason the developer has to fix, and finding out after writing package.json
|
|
79
|
+
// and .env.local is exactly the half-install this must not produce.
|
|
80
|
+
const strategy = strategies.find((s) => s.detect(fs, root) !== null);
|
|
81
|
+
if (!strategy) {
|
|
82
|
+
throw new InjectionError([
|
|
83
|
+
"could not find a root layout to inject the toolbar into.",
|
|
84
|
+
`Tried: ${strategies.map((s) => s.label).join(", ")}.`,
|
|
85
|
+
"Nothing was changed. Add the provider by hand instead:",
|
|
86
|
+
"",
|
|
87
|
+
` import { FeedbackToolbar } from "${PACKAGE_NAME}";`,
|
|
88
|
+
" // ...then render <FeedbackToolbar /> inside <body> in your root layout.",
|
|
89
|
+
].join("\n"));
|
|
90
|
+
}
|
|
91
|
+
const site = strategy.detect(fs, root);
|
|
92
|
+
const layoutSource = fs.readFile(site.path);
|
|
93
|
+
const alreadyInjected = strategy.isInstalled(layoutSource);
|
|
94
|
+
// Computed before any write: `inject` throws rather than returning a
|
|
95
|
+
// half-edited file, and this is where that throw has to happen.
|
|
96
|
+
const injected = alreadyInjected
|
|
97
|
+
? layoutSource
|
|
98
|
+
: strategy.inject(layoutSource, {
|
|
99
|
+
apiUrl,
|
|
100
|
+
projectUrl,
|
|
101
|
+
environment: options.environment,
|
|
102
|
+
});
|
|
103
|
+
// A script-tag install has no dependency to add, no env file to read at
|
|
104
|
+
// runtime, and no build to hook - the toolbar is a file served from the
|
|
105
|
+
// backend and configured from the tag's own attributes. Skipping those
|
|
106
|
+
// steps is the whole difference between the two delivery routes.
|
|
107
|
+
const scriptTag = strategy.delivery === "script-tag";
|
|
108
|
+
const changes = [];
|
|
109
|
+
const notes = [];
|
|
110
|
+
let packageJsonChanged = false;
|
|
111
|
+
// 1. Dependency. Resolved here rather than by the caller because this is
|
|
112
|
+
// the only step that needs it, and resolved BEFORE the first write like
|
|
113
|
+
// everything else that can fail: a refusal has to leave the tree
|
|
114
|
+
// untouched.
|
|
115
|
+
if (!scriptTag) {
|
|
116
|
+
const deps = pkg.dependencies ?? {};
|
|
117
|
+
if (!deps[PACKAGE_NAME]) {
|
|
118
|
+
if (!options.resolveToolbarRange) {
|
|
119
|
+
throw new InjectionError(`could not tell which version of ${PACKAGE_NAME} to install. ` +
|
|
120
|
+
"Re-run with --toolbar-version <range>.");
|
|
121
|
+
}
|
|
122
|
+
const range = await options.resolveToolbarRange();
|
|
123
|
+
pkg.dependencies = { ...deps, [PACKAGE_NAME]: range };
|
|
124
|
+
packageJsonChanged = true;
|
|
125
|
+
changes.push(`added ${PACKAGE_NAME}@${range} to dependencies`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// 2. Env var, in whichever env file already exists, else .env.local.
|
|
129
|
+
//
|
|
130
|
+
// A local-development convenience, and only that. It is deliberately NOT
|
|
131
|
+
// how the deployed toolbar finds the backend: `.env.local` is gitignored
|
|
132
|
+
// by create-next-app, so it never reaches a build, which is why the URL
|
|
133
|
+
// is written into the injected component and into the prebuild command
|
|
134
|
+
// as well. Kept because a developer running `next dev` before their
|
|
135
|
+
// first deploy expects the variable to be there.
|
|
136
|
+
if (!scriptTag) {
|
|
137
|
+
const envPath = ENV_FILES.map((f) => joinPath(root, f)).find((p) => fs.exists(p));
|
|
138
|
+
const targetEnvPath = envPath ?? joinPath(root, ENV_FILES[0]);
|
|
139
|
+
const existingEnv = envPath ? fs.readFile(envPath) : "";
|
|
140
|
+
if (!existingEnv.includes(`${API_URL_ENV_VAR}=`)) {
|
|
141
|
+
const separator = existingEnv.length === 0 || existingEnv.endsWith("\n") ? "" : "\n";
|
|
142
|
+
fs.writeFile(targetEnvPath, `${existingEnv}${separator}${API_URL_ENV_VAR}=${apiUrl}\n`);
|
|
143
|
+
changes.push(`wrote ${API_URL_ENV_VAR} to ${targetEnvPath} (local development; the ` +
|
|
144
|
+
"deployed toolbar reads the URL from the component it injected)");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// 3. The provider.
|
|
148
|
+
if (!alreadyInjected) {
|
|
149
|
+
fs.writeFile(site.path, injected);
|
|
150
|
+
const what = scriptTag
|
|
151
|
+
? `injected the ${SCRIPT_BUNDLE_PATH} script tag into the ${site.description}`
|
|
152
|
+
: `injected <FeedbackToolbar /> into the ${site.description}`;
|
|
153
|
+
changes.push(what);
|
|
154
|
+
if (scriptTag) {
|
|
155
|
+
// The gate cannot be resolved from a static file - absent means off - so
|
|
156
|
+
// the tag has to declare one or it mounts nothing. Written rather than
|
|
157
|
+
// left to the developer, and said out loud here because it is the one
|
|
158
|
+
// value in the install that decides whether their own customers see a
|
|
159
|
+
// feedback widget.
|
|
160
|
+
const environment = options.environment ?? DEFAULT_SCRIPT_ENVIRONMENT;
|
|
161
|
+
notes.push(`the tag declares data-environment="${environment}", which is what makes it mount. ` +
|
|
162
|
+
"Change it to production on any page real customers reach, or re-run with " +
|
|
163
|
+
"--environment production.");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// 4. The build hook. This is the step #11 was reopened for: without it,
|
|
167
|
+
// nothing ever registers the project except a developer who happened to
|
|
168
|
+
// pass two flags by hand.
|
|
169
|
+
if (!scriptTag) {
|
|
170
|
+
const wired = wirePrebuild(pkg, apiUrl);
|
|
171
|
+
if (wired) {
|
|
172
|
+
packageJsonChanged = true;
|
|
173
|
+
changes.push(wired);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (packageJsonChanged) {
|
|
177
|
+
fs.writeFile(packageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
178
|
+
}
|
|
179
|
+
// 5. Register the project, if this machine happens to know enough to. On a
|
|
180
|
+
// developer's laptop it usually does not, and that is fine now: step 4
|
|
181
|
+
// put the command in the build, where the URL and the key live.
|
|
182
|
+
const apiKey = options.apiKey ?? env[API_KEY_ENV_VAR];
|
|
183
|
+
if (!projectUrl || !apiKey) {
|
|
184
|
+
notes.push(registrationDeferredNote({ projectUrl, apiKey, scriptTag }));
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
try {
|
|
188
|
+
const outcome = await register({
|
|
189
|
+
apiUrl,
|
|
190
|
+
apiKey,
|
|
191
|
+
projectUrl,
|
|
192
|
+
packageName: pkg.name,
|
|
193
|
+
env,
|
|
194
|
+
registerProject: options.registerProject,
|
|
195
|
+
});
|
|
196
|
+
if (outcome.created) {
|
|
197
|
+
changes.push(`registered ${projectUrl} as a project`);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
notes.push(`${projectUrl} was already registered`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
// A registration that did not go through is not a failed install: the
|
|
205
|
+
// code is in place and the next build registers. Say so and carry on.
|
|
206
|
+
notes.push(`could not register ${projectUrl} (${String(err)}); it will register on the next build`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
changes,
|
|
211
|
+
notes,
|
|
212
|
+
alreadyInstalled: changes.length === 0,
|
|
213
|
+
strategy: strategy.id,
|
|
214
|
+
delivery: scriptTag ? "script-tag" : "npm-package",
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Add `feedback-toolbar register` to the host app's `prebuild`, once.
|
|
219
|
+
*
|
|
220
|
+
* npm, pnpm and yarn all run `prebuild` before `build`, which is why the hook
|
|
221
|
+
* goes there rather than into `build` itself: appending to somebody's build
|
|
222
|
+
* command changes the command they debug, and prepending to it changes what
|
|
223
|
+
* their CI logs look like at the top. `prebuild` is the slot the package
|
|
224
|
+
* managers reserve for exactly this.
|
|
225
|
+
*
|
|
226
|
+
* `--optional` is not decoration. This runs on every deploy of somebody
|
|
227
|
+
* else's product forever, and a feedback tool that fails a deploy because our
|
|
228
|
+
* backend was slow, or because a key was rotated, has done far more damage
|
|
229
|
+
* than the missing project row it was trying to create.
|
|
230
|
+
*
|
|
231
|
+
* Refusing to add it twice is the same idempotency rule as everything else
|
|
232
|
+
* here, and it has to survive the developer having edited the line: the check
|
|
233
|
+
* is for the command, not for the exact string this wrote. Like every other
|
|
234
|
+
* step, a second run says nothing rather than reporting a non-change.
|
|
235
|
+
*
|
|
236
|
+
* Returns the line to report, or undefined when there was nothing to do.
|
|
237
|
+
*/
|
|
238
|
+
function wirePrebuild(pkg, apiUrl) {
|
|
239
|
+
const scripts = pkg.scripts ?? {};
|
|
240
|
+
const existing = scripts.prebuild;
|
|
241
|
+
const line = prebuildScript(apiUrl);
|
|
242
|
+
if (existing === undefined) {
|
|
243
|
+
pkg.scripts = { ...scripts, prebuild: line };
|
|
244
|
+
return `added a "prebuild" script that registers this deployment on every build: ${line}`;
|
|
245
|
+
}
|
|
246
|
+
if (existing.includes(REGISTER_COMMAND))
|
|
247
|
+
return undefined;
|
|
248
|
+
// Their command first, ours after it, joined rather than replaced: the
|
|
249
|
+
// existing prebuild is somebody's build prerequisite and must still run,
|
|
250
|
+
// and must still be the thing that fails the build if it fails.
|
|
251
|
+
pkg.scripts = { ...scripts, prebuild: `${existing} && ${line}` };
|
|
252
|
+
return (`edited the existing "prebuild" script to also register this deployment: ` +
|
|
253
|
+
`${existing} && ${line}`);
|
|
254
|
+
}
|
|
255
|
+
/** Why registration did not happen here, and what will do it instead. */
|
|
256
|
+
function registrationDeferredNote(input) {
|
|
257
|
+
if (input.scriptTag) {
|
|
258
|
+
// No build to hook, so nothing will do this on its own. #17's answer: run
|
|
259
|
+
// the command once, from a terminal or CI, where a key belongs.
|
|
260
|
+
return ("a script-tag install has no build step, so nothing registers the project for you. " +
|
|
261
|
+
`Run it once: npx ${PACKAGE_NAME} register --project-url <url> --api-key <key>`);
|
|
262
|
+
}
|
|
263
|
+
if (!input.apiKey) {
|
|
264
|
+
return ("no deploy key here, so nothing was registered yet. The prebuild step will do it " +
|
|
265
|
+
`on the next build, and it needs one thing from you: set ${API_KEY_ENV_VAR} in your ` +
|
|
266
|
+
"hosting provider's environment variables (dashboard > Settings > Deploy keys). " +
|
|
267
|
+
"The backend URL is already baked into the prebuild command and into the injected " +
|
|
268
|
+
"component, so there is nothing else to configure.");
|
|
269
|
+
}
|
|
270
|
+
return ("this machine does not report a deployment URL, so nothing was registered yet - " +
|
|
271
|
+
"the prebuild step will do it on the next build, where VERCEL_URL is set");
|
|
272
|
+
}
|
|
273
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { type RegisterProjectTransport } from "../registration.js";
|
|
2
|
+
/** Where `--api-url` falls back to. The public name is what a build already has. */
|
|
3
|
+
export declare const API_URL_ENV_VARS: readonly ["NEXT_PUBLIC_FEEDBACK_TOOLBAR_API_URL", "FEEDBACK_TOOLBAR_API_URL"];
|
|
4
|
+
/**
|
|
5
|
+
* Where `--api-key` falls back to, and the only place a key is ever read from.
|
|
6
|
+
* Never a file, never a `data-` attribute, never an argument this CLI writes
|
|
7
|
+
* down: a deploy key's whole power is creating projects, so it lives in the
|
|
8
|
+
* build environment and nowhere else.
|
|
9
|
+
*/
|
|
10
|
+
export declare const API_KEY_ENV_VAR = "FEEDBACK_TOOLBAR_API_KEY";
|
|
11
|
+
export interface RegisterOptions {
|
|
12
|
+
/** `--api-url`, else one of API_URL_ENV_VARS. */
|
|
13
|
+
apiUrl?: string | undefined;
|
|
14
|
+
/** `--api-key`, else API_KEY_ENV_VAR. */
|
|
15
|
+
apiKey?: string | undefined;
|
|
16
|
+
/** `--project-url`, else whatever the build environment reports. */
|
|
17
|
+
projectUrl?: string | undefined;
|
|
18
|
+
/** The host app's package name, as a fallback for naming the project. */
|
|
19
|
+
packageName?: string | undefined;
|
|
20
|
+
/** The build environment. Explicit so this stays a pure function. */
|
|
21
|
+
env: Record<string, string | undefined>;
|
|
22
|
+
/** Injected so tests do not reach the network. */
|
|
23
|
+
registerProject?: RegisterProjectTransport;
|
|
24
|
+
}
|
|
25
|
+
export interface RegisterOutcome {
|
|
26
|
+
projectUrl: string;
|
|
27
|
+
/** False when the URL was already registered, which is a success. */
|
|
28
|
+
created: boolean;
|
|
29
|
+
/** What the backend called it, when it said. */
|
|
30
|
+
projectName?: string | undefined;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Register the deployment, or confirm it is already registered.
|
|
34
|
+
*
|
|
35
|
+
* Idempotent because the backend is: the second call with the same normalised
|
|
36
|
+
* URL is a no-op that answers `created: false`. That is what makes this safe
|
|
37
|
+
* as a prebuild step, which runs on every deploy forever.
|
|
38
|
+
*
|
|
39
|
+
* Throws `RegistrationError` with a message naming the missing piece when it
|
|
40
|
+
* cannot proceed. It never guesses: a registration under a URL nobody meant
|
|
41
|
+
* creates a project that collects feedback from a page that does not exist.
|
|
42
|
+
*/
|
|
43
|
+
export declare function register(options: RegisterOptions): Promise<RegisterOutcome>;
|
|
44
|
+
/**
|
|
45
|
+
* The URL to register the project under, from the build environment.
|
|
46
|
+
*
|
|
47
|
+
* Same precedence as the toolbar's own `resolveConfig`, and that agreement is
|
|
48
|
+
* the point: the toolbar reports this value back at runtime, so if the two
|
|
49
|
+
* picked differently the lookup would miss on every preview deployment. The
|
|
50
|
+
* production hostname wins because a preview URL changes on every commit, and
|
|
51
|
+
* keying on it would create a project per deploy instead of collecting a
|
|
52
|
+
* project's feedback in one place.
|
|
53
|
+
*/
|
|
54
|
+
export declare function inferProjectUrl(env: Record<string, string | undefined>): string | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* The browser origins this deployment serves the toolbar from.
|
|
57
|
+
*
|
|
58
|
+
* A DIFFERENT question from `inferProjectUrl`, and conflating them is what
|
|
59
|
+
* made the toolbar unusable everywhere it was installed. That function answers
|
|
60
|
+
* "which project is this", and answers it with the production URL because a
|
|
61
|
+
* project's feedback has to collect in one place across every deploy. This one
|
|
62
|
+
* answers "where will a browser be calling us from", and on a Vercel install
|
|
63
|
+
* the answer is never the production URL: the toolbar is off in production by
|
|
64
|
+
* design, so it only ever runs on a preview, whose origin is per-branch or
|
|
65
|
+
* per-commit.
|
|
66
|
+
*
|
|
67
|
+
* Both are reported, in this order:
|
|
68
|
+
*
|
|
69
|
+
* - `VERCEL_BRANCH_URL` - `<project>-git-<branch>-<team>.vercel.app`. Stable
|
|
70
|
+
* for the life of the branch, and the URL a reviewer is actually sent, so
|
|
71
|
+
* it is the one that matters. It is also bounded by branch count rather
|
|
72
|
+
* than by deploy count, which is what keeps the allow-list from growing
|
|
73
|
+
* without limit.
|
|
74
|
+
* - `VERCEL_URL` - this deployment alone. Included because a reviewer handed
|
|
75
|
+
* a specific deployment's URL is a real case, and because a build with no
|
|
76
|
+
* branch alias (a `vercel deploy` from a terminal) has nothing else.
|
|
77
|
+
*
|
|
78
|
+
* Empty off Vercel, where neither exists and there is no preview to allow.
|
|
79
|
+
* The backend normalises and de-duplicates, and drops the project's own URL,
|
|
80
|
+
* so this does not have to be careful about overlap - only about never
|
|
81
|
+
* inventing a host it was not told.
|
|
82
|
+
*/
|
|
83
|
+
export declare function inferProjectOrigins(env: Record<string, string | undefined>): string[];
|
|
84
|
+
//# sourceMappingURL=register.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../src/commands/register.ts"],"names":[],"mappings":"AAeA,OAAO,EAGL,KAAK,wBAAwB,EAC9B,MAAM,iBAAiB,CAAC;AAEzB,oFAAoF;AACpF,eAAO,MAAM,gBAAgB,+EAGnB,CAAC;AAEX;;;;;GAKG;AACH,eAAO,MAAM,eAAe,6BAA6B,CAAC;AAE1D,MAAM,WAAW,eAAe;IAC9B,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,yCAAyC;IACzC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,yEAAyE;IACzE,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACxC,kDAAkD;IAClD,eAAe,CAAC,EAAE,wBAAwB,CAAC;CAC5C;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,OAAO,EAAE,OAAO,CAAC;IACjB,gDAAgD;IAChD,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,QAAQ,CAC5B,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,eAAe,CAAC,CA6C1B;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GACtC,MAAM,GAAG,SAAS,CAGpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GACtC,MAAM,EAAE,CAKV"}
|