@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.
Files changed (42) hide show
  1. package/README.md +38 -0
  2. package/dist/bin.d.ts +3 -0
  3. package/dist/bin.d.ts.map +1 -0
  4. package/dist/bin.js +24 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/commands/init.d.ts +97 -0
  7. package/dist/commands/init.d.ts.map +1 -0
  8. package/dist/commands/init.js +273 -0
  9. package/dist/commands/init.js.map +1 -0
  10. package/dist/commands/register.d.ts +84 -0
  11. package/dist/commands/register.d.ts.map +1 -0
  12. package/dist/commands/register.js +125 -0
  13. package/dist/commands/register.js.map +1 -0
  14. package/dist/index.d.ts +2 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +9 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/install/env.d.ts +2 -0
  19. package/dist/install/env.d.ts.map +1 -0
  20. package/dist/install/env.js +19 -0
  21. package/dist/install/env.js.map +1 -0
  22. package/dist/install/fs.d.ts +16 -0
  23. package/dist/install/fs.d.ts.map +1 -0
  24. package/dist/install/fs.js +33 -0
  25. package/dist/install/fs.js.map +1 -0
  26. package/dist/install/strategies.d.ts +109 -0
  27. package/dist/install/strategies.d.ts.map +1 -0
  28. package/dist/install/strategies.js +208 -0
  29. package/dist/install/strategies.js.map +1 -0
  30. package/dist/registration.d.ts +43 -0
  31. package/dist/registration.d.ts.map +1 -0
  32. package/dist/registration.js +84 -0
  33. package/dist/registration.js.map +1 -0
  34. package/dist/run.d.ts +14 -0
  35. package/dist/run.d.ts.map +1 -0
  36. package/dist/run.js +293 -0
  37. package/dist/run.js.map +1 -0
  38. package/dist/toolbarVersion.d.ts +36 -0
  39. package/dist/toolbarVersion.d.ts.map +1 -0
  40. package/dist/toolbarVersion.js +106 -0
  41. package/dist/toolbarVersion.js.map +1 -0
  42. package/package.json +46 -0
@@ -0,0 +1,125 @@
1
+ // `feedback-toolbar register` - make sure this deployment exists as a project.
2
+ //
3
+ // A first-class command, not a step inside `init`, and issues #11 and #17 both
4
+ // needed it to be:
5
+ //
6
+ // - #11 reopened because `init` runs on a laptop, where VERCEL_URL does not
7
+ // exist, so the project URL was undefined and registration was silently
8
+ // skipped. The fix is to register where those variables DO exist: in the
9
+ // build. `init` wires this command in as a prebuild step.
10
+ // - #17 added the script-tag install, which has no build to hook at all. Its
11
+ // project is registered by running this command once from a terminal or CI.
12
+ //
13
+ // Both callers want the same three things: resolve where the values come from,
14
+ // call the one registration service, and say clearly what happened. None of
15
+ // them wants a second opinion on what a valid URL is - see ./registration.ts.
16
+ import { RegistrationError, postRegistration, } from "../registration.js";
17
+ /** Where `--api-url` falls back to. The public name is what a build already has. */
18
+ export const API_URL_ENV_VARS = [
19
+ "NEXT_PUBLIC_FEEDBACK_TOOLBAR_API_URL",
20
+ "FEEDBACK_TOOLBAR_API_URL",
21
+ ];
22
+ /**
23
+ * Where `--api-key` falls back to, and the only place a key is ever read from.
24
+ * Never a file, never a `data-` attribute, never an argument this CLI writes
25
+ * down: a deploy key's whole power is creating projects, so it lives in the
26
+ * build environment and nowhere else.
27
+ */
28
+ export const API_KEY_ENV_VAR = "FEEDBACK_TOOLBAR_API_KEY";
29
+ /**
30
+ * Register the deployment, or confirm it is already registered.
31
+ *
32
+ * Idempotent because the backend is: the second call with the same normalised
33
+ * URL is a no-op that answers `created: false`. That is what makes this safe
34
+ * as a prebuild step, which runs on every deploy forever.
35
+ *
36
+ * Throws `RegistrationError` with a message naming the missing piece when it
37
+ * cannot proceed. It never guesses: a registration under a URL nobody meant
38
+ * creates a project that collects feedback from a page that does not exist.
39
+ */
40
+ export async function register(options) {
41
+ const { env } = options;
42
+ const transport = options.registerProject ?? postRegistration;
43
+ const apiUrl = options.apiUrl ?? API_URL_ENV_VARS.map((name) => env[name]).find(Boolean);
44
+ if (!apiUrl) {
45
+ throw new RegistrationError("no backend URL. Pass --api-url https://feedback.bitvea.com, or set " +
46
+ `${API_URL_ENV_VARS[0]} in the build environment (\`init\` writes it into .env.local).`);
47
+ }
48
+ const apiKey = options.apiKey ?? env[API_KEY_ENV_VAR];
49
+ if (!apiKey) {
50
+ throw new RegistrationError(`no deploy key. Pass --api-key, or set ${API_KEY_ENV_VAR} in the build ` +
51
+ "environment. Create one in the dashboard under Settings > Deploy keys. " +
52
+ "It must never be committed or put in a page.");
53
+ }
54
+ const projectUrl = options.projectUrl ?? inferProjectUrl(env);
55
+ if (!projectUrl) {
56
+ throw new RegistrationError("could not tell which URL to register. Pass --project-url https://myapp.com. " +
57
+ "Inside a Vercel build this is read from VERCEL_PROJECT_PRODUCTION_URL or " +
58
+ "VERCEL_URL, neither of which is set here.");
59
+ }
60
+ const { created, project } = await transport({
61
+ apiUrl,
62
+ apiKey,
63
+ projectUrl,
64
+ repoOwner: env.VERCEL_GIT_REPO_OWNER,
65
+ repoSlug: env.VERCEL_GIT_REPO_SLUG,
66
+ packageName: options.packageName,
67
+ // Undefined rather than an empty array off Vercel: "I have nothing to
68
+ // report" and "I report an empty list" are the same thing here, and the
69
+ // shorter one keeps the request the shape it has always had.
70
+ origins: emptyToUndefined(inferProjectOrigins(env)),
71
+ });
72
+ return { projectUrl, created, projectName: project?.name };
73
+ }
74
+ /**
75
+ * The URL to register the project under, from the build environment.
76
+ *
77
+ * Same precedence as the toolbar's own `resolveConfig`, and that agreement is
78
+ * the point: the toolbar reports this value back at runtime, so if the two
79
+ * picked differently the lookup would miss on every preview deployment. The
80
+ * production hostname wins because a preview URL changes on every commit, and
81
+ * keying on it would create a project per deploy instead of collecting a
82
+ * project's feedback in one place.
83
+ */
84
+ export function inferProjectUrl(env) {
85
+ const host = env.VERCEL_PROJECT_PRODUCTION_URL ?? env.VERCEL_URL;
86
+ return host ? `https://${host}` : undefined;
87
+ }
88
+ /**
89
+ * The browser origins this deployment serves the toolbar from.
90
+ *
91
+ * A DIFFERENT question from `inferProjectUrl`, and conflating them is what
92
+ * made the toolbar unusable everywhere it was installed. That function answers
93
+ * "which project is this", and answers it with the production URL because a
94
+ * project's feedback has to collect in one place across every deploy. This one
95
+ * answers "where will a browser be calling us from", and on a Vercel install
96
+ * the answer is never the production URL: the toolbar is off in production by
97
+ * design, so it only ever runs on a preview, whose origin is per-branch or
98
+ * per-commit.
99
+ *
100
+ * Both are reported, in this order:
101
+ *
102
+ * - `VERCEL_BRANCH_URL` - `<project>-git-<branch>-<team>.vercel.app`. Stable
103
+ * for the life of the branch, and the URL a reviewer is actually sent, so
104
+ * it is the one that matters. It is also bounded by branch count rather
105
+ * than by deploy count, which is what keeps the allow-list from growing
106
+ * without limit.
107
+ * - `VERCEL_URL` - this deployment alone. Included because a reviewer handed
108
+ * a specific deployment's URL is a real case, and because a build with no
109
+ * branch alias (a `vercel deploy` from a terminal) has nothing else.
110
+ *
111
+ * Empty off Vercel, where neither exists and there is no preview to allow.
112
+ * The backend normalises and de-duplicates, and drops the project's own URL,
113
+ * so this does not have to be careful about overlap - only about never
114
+ * inventing a host it was not told.
115
+ */
116
+ export function inferProjectOrigins(env) {
117
+ const hosts = [env.VERCEL_BRANCH_URL, env.VERCEL_URL];
118
+ return hosts
119
+ .filter((host) => Boolean(host))
120
+ .map((host) => `https://${host}`);
121
+ }
122
+ function emptyToUndefined(values) {
123
+ return values.length > 0 ? values : undefined;
124
+ }
125
+ //# sourceMappingURL=register.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.js","sourceRoot":"","sources":["../../src/commands/register.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { run, type RunOptions } from "./run.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,GAAG,EAAE,KAAK,UAAU,EAAE,MAAM,OAAO,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ // The package's public surface, and it is deliberately one function.
2
+ //
3
+ // `@bitvea/feedback-toolbar`'s bin imports `run` and calls it in its own
4
+ // process, which is what makes `npx @bitvea/feedback-toolbar init` and the
5
+ // `prebuild` line committed in customers' repositories behave exactly as they
6
+ // did when the installer lived in that package. Anything else exported from
7
+ // here would be a second contract to keep across two release lines.
8
+ export { run } from "./run.js";
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,EAAE;AACF,yEAAyE;AACzE,2EAA2E;AAC3E,8EAA8E;AAC9E,4EAA4E;AAC5E,oEAAoE;AACpE,OAAO,EAAE,GAAG,EAAmB,MAAM,OAAO,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const API_URL_ENV_VAR = "NEXT_PUBLIC_FEEDBACK_TOOLBAR_API_URL";
2
+ //# sourceMappingURL=env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/install/env.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,eAAe,yCAAyC,CAAC"}
@@ -0,0 +1,19 @@
1
+ // The environment-variable names this installer writes, and who reads them.
2
+ //
3
+ // These are a contract between two artefacts that no longer share a package:
4
+ // the CLI writes `NEXT_PUBLIC_FEEDBACK_TOOLBAR_API_URL`, and
5
+ // `packages/toolbar/src/config.ts` reads it back out of the host's build. The
6
+ // literal is duplicated here rather than imported, because an import would be
7
+ // a `@bitvea/*` specifier in this package's emitted dist/ - the one thing that
8
+ // would stop the published tarball standing on its own with no node_modules
9
+ // at all, which is the strongest assertion its publishable check makes.
10
+ //
11
+ // Duplication that nobody checks is drift, and drift here is the class of
12
+ // silent failure the install path is already full of: a variable written under
13
+ // one name and read under another produces a toolbar that mounts and never
14
+ // works. So `env.test.ts` reads packages/toolbar/src/config.ts off disk and
15
+ // asserts the two strings match. That test is monorepo-only and fails loudly
16
+ // if the file moves, which is the correct behaviour: the pair has to be looked
17
+ // at together.
18
+ export const API_URL_ENV_VAR = "NEXT_PUBLIC_FEEDBACK_TOOLBAR_API_URL";
19
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/install/env.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,EAAE;AACF,6EAA6E;AAC7E,6DAA6D;AAC7D,8EAA8E;AAC9E,8EAA8E;AAC9E,+EAA+E;AAC/E,4EAA4E;AAC5E,wEAAwE;AACxE,EAAE;AACF,0EAA0E;AAC1E,+EAA+E;AAC/E,2EAA2E;AAC3E,4EAA4E;AAC5E,6EAA6E;AAC7E,+EAA+E;AAC/E,eAAe;AACf,MAAM,CAAC,MAAM,eAAe,GAAG,sCAAsC,CAAC"}
@@ -0,0 +1,16 @@
1
+ export interface InstallerFs {
2
+ exists(path: string): boolean;
3
+ readFile(path: string): string;
4
+ writeFile(path: string, contents: string): void;
5
+ }
6
+ /** An in-memory tree, which is what the installer's tests run against. */
7
+ export declare class MemoryFs implements InstallerFs {
8
+ readonly files: Map<string, string>;
9
+ constructor(files?: Record<string, string>);
10
+ exists(path: string): boolean;
11
+ readFile(path: string): string;
12
+ writeFile(path: string, contents: string): void;
13
+ }
14
+ /** Join path segments with `/`, collapsing the duplicates that creep in. */
15
+ export declare function joinPath(...parts: string[]): string;
16
+ //# sourceMappingURL=fs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/install/fs.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACjD;AAED,0EAA0E;AAC1E,qBAAa,QAAS,YAAW,WAAW;IAC1C,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;gBAExB,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM;IAI9C,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI7B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAM9B,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;CAGhD;AAED,4EAA4E;AAC5E,wBAAgB,QAAQ,CAAC,GAAG,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAKnD"}
@@ -0,0 +1,33 @@
1
+ // The filesystem the installer talks to, as an interface.
2
+ //
3
+ // The installer edits somebody else's repository, so its tests need to run
4
+ // against a tree they fully control, and a codemod tested only against the
5
+ // real disk is a codemod nobody dares change. Everything below the interface
6
+ // is a handful of thin wrappers; everything above it is pure logic.
7
+ /** An in-memory tree, which is what the installer's tests run against. */
8
+ export class MemoryFs {
9
+ files;
10
+ constructor(files = {}) {
11
+ this.files = new Map(Object.entries(files));
12
+ }
13
+ exists(path) {
14
+ return this.files.has(path);
15
+ }
16
+ readFile(path) {
17
+ const contents = this.files.get(path);
18
+ if (contents === undefined)
19
+ throw new Error(`no such file: ${path}`);
20
+ return contents;
21
+ }
22
+ writeFile(path, contents) {
23
+ this.files.set(path, contents);
24
+ }
25
+ }
26
+ /** Join path segments with `/`, collapsing the duplicates that creep in. */
27
+ export function joinPath(...parts) {
28
+ return parts
29
+ .filter((p) => p.length > 0)
30
+ .join("/")
31
+ .replace(/\/{2,}/g, "/");
32
+ }
33
+ //# sourceMappingURL=fs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.js","sourceRoot":"","sources":["../../src/install/fs.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAC1D,EAAE;AACF,2EAA2E;AAC3E,2EAA2E;AAC3E,6EAA6E;AAC7E,oEAAoE;AAQpE,0EAA0E;AAC1E,MAAM,OAAO,QAAQ;IACV,KAAK,CAAsB;IAEpC,YAAY,QAAgC,EAAE;QAC5C,IAAI,CAAC,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM,CAAC,IAAY;QACjB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED,QAAQ,CAAC,IAAY;QACnB,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,QAAQ,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC;QACrE,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,SAAS,CAAC,IAAY,EAAE,QAAgB;QACtC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACjC,CAAC;CACF;AAED,4EAA4E;AAC5E,MAAM,UAAU,QAAQ,CAAC,GAAG,KAAe;IACzC,OAAO,KAAK;SACT,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;SAC3B,IAAI,CAAC,GAAG,CAAC;SACT,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,109 @@
1
+ import { type InstallerFs } from "./fs.js";
2
+ /**
3
+ * The package `init` writes into a host app's dependencies and imports from in
4
+ * their layout.
5
+ *
6
+ * It is the name of a DIFFERENT package from this one, and that is the point:
7
+ * it is data this installer writes into somebody else's repository, never a
8
+ * module specifier. Nothing here imports the toolbar, and nothing here may -
9
+ * an emitted `@bitvea/*` specifier is what would stop this tarball standing on
10
+ * its own with no node_modules at all.
11
+ */
12
+ export declare const PACKAGE_NAME = "@bitvea/feedback-toolbar";
13
+ export declare const COMPONENT_NAME = "FeedbackToolbar";
14
+ /** The file the script-tag route is served as, relative to the backend URL. */
15
+ export declare const SCRIPT_BUNDLE_PATH = "/toolbar.js";
16
+ export interface InjectionSite {
17
+ /** The file to edit. */
18
+ path: string;
19
+ /** Human-readable description of what was found, for the CLI's output. */
20
+ description: string;
21
+ }
22
+ /**
23
+ * How a strategy's host app gets the toolbar's *code*, which decides which of
24
+ * the installer's other steps apply at all.
25
+ *
26
+ * `npm-package`: the toolbar is a dependency the host's bundler compiles, so
27
+ * the install needs a package.json entry, an env var for the API URL, and a
28
+ * prebuild hook to register the project from the build.
29
+ *
30
+ * `script-tag`: the toolbar is a file served from the backend and configured
31
+ * from `data-` attributes on the tag. There is no dependency to add, no env
32
+ * file to read at runtime, and - the reason #17 called this out - no build to
33
+ * hook, so its project is registered by running `register` by hand.
34
+ */
35
+ export type Delivery = "npm-package" | "script-tag";
36
+ /**
37
+ * The environment a script-tag install declares, when the developer has not
38
+ * said otherwise.
39
+ *
40
+ * The gate treats "absent" as off, and a static HTML file cannot know which
41
+ * deployment served it - so a tag with no `data-environment` mounts nothing,
42
+ * ever. That is the correct default for a value we could only guess, and the
43
+ * wrong default for an installer whose whole job is to leave a working
44
+ * install behind: #17 asks for a toolbar that mounts on a plain page with no
45
+ * build step, and a tag the developer has to hand-edit is not that.
46
+ *
47
+ * So `init` writes an environment, and the value is a review environment
48
+ * because installing a review tool on a page is what the command means. The
49
+ * result is printed as a change and called out as a note, and `--environment`
50
+ * overrides it - including with `production`, which is how somebody turns the
51
+ * tag off without removing it.
52
+ */
53
+ export declare const DEFAULT_SCRIPT_ENVIRONMENT = "preview";
54
+ /** What `inject` needs to know about the install to write the markup. */
55
+ export interface InjectionContext {
56
+ /** Base URL of the BitVea Feedback backend, no trailing slash. */
57
+ apiUrl: string;
58
+ /** The deployment URL the project is registered under, when known. */
59
+ projectUrl?: string | undefined;
60
+ /**
61
+ * What the injected markup declares as its environment. Only the script-tag
62
+ * route uses it: an npm install resolves the environment from whatever its
63
+ * host's build populates - see buildEnv.ts - and a static page has no build
64
+ * provider to be resolved from at all.
65
+ */
66
+ environment?: string | undefined;
67
+ }
68
+ export interface InjectionStrategy {
69
+ /** Stable id, reported in the CLI's output. */
70
+ readonly id: string;
71
+ readonly label: string;
72
+ /** Which of the installer's steps apply. Defaults to `npm-package`. */
73
+ readonly delivery?: Delivery;
74
+ /** Find the file to inject into, or null when this strategy does not apply. */
75
+ detect(fs: InstallerFs, root: string): InjectionSite | null;
76
+ /** True when the provider is already there, which makes a re-run a no-op. */
77
+ isInstalled(source: string): boolean;
78
+ /**
79
+ * Return the edited source. Only called when `isInstalled` is false, and
80
+ * throws when it cannot do the edit, so a partial write is not possible:
81
+ * the caller writes only what this returns.
82
+ */
83
+ inject(source: string, context: InjectionContext): string;
84
+ }
85
+ export declare const nextAppRouterStrategy: InjectionStrategy;
86
+ /**
87
+ * The `<script src>` install, for an app with no build step to add a
88
+ * dependency to. Second in the list on purpose: an App Router app usually
89
+ * also has an index.html somewhere, and the npm install is the better one.
90
+ *
91
+ * Note what the injected tag does NOT carry: a deploy key. There is no
92
+ * supported way to put one in a page, and the bundle refuses to mount when it
93
+ * finds anything that looks like one - a key in a page is a key in every
94
+ * visitor's hands, and its whole power is creating projects.
95
+ *
96
+ * It does carry an environment, and it has to. A static file cannot know which
97
+ * deployment served it and the gate treats absent as off, so a tag without one
98
+ * mounts nothing at all - which is what #17's "mounts a working toolbar on a
99
+ * plain HTML page" cannot mean. The value is written, printed as a change, and
100
+ * called out as a note; `--environment production` is how a developer turns
101
+ * the tag off without deleting it. See DEFAULT_SCRIPT_ENVIRONMENT.
102
+ */
103
+ export declare const htmlScriptTagStrategy: InjectionStrategy;
104
+ /** A failure the developer can act on, as opposed to an internal error. */
105
+ export declare class InjectionError extends Error {
106
+ constructor(message: string);
107
+ }
108
+ export declare const STRATEGIES: readonly InjectionStrategy[];
109
+ //# sourceMappingURL=strategies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strategies.d.ts","sourceRoot":"","sources":["../../src/install/strategies.ts"],"names":[],"mappings":"AAOA,OAAO,EAAY,KAAK,WAAW,EAAE,MAAM,MAAM,CAAC;AAElD;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,6BAA6B,CAAC;AACvD,eAAO,MAAM,cAAc,oBAAoB,CAAC;AAEhD,+EAA+E;AAC/E,eAAO,MAAM,kBAAkB,gBAAgB,CAAC;AAEhD,MAAM,WAAW,aAAa;IAC5B,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,QAAQ,GAAG,aAAa,GAAG,YAAY,CAAC;AAEpD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,0BAA0B,YAAY,CAAC;AAEpD,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC/B,kEAAkE;IAClE,MAAM,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC;AAED,MAAM,WAAW,iBAAiB;IAChC,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,uEAAuE;IACvE,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAC7B,+EAA+E;IAC/E,MAAM,CAAC,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAAC;IAC5D,6EAA6E;IAC7E,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IACrC;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAAC;CAC3D;AAUD,eAAO,MAAM,qBAAqB,EAAE,iBAwDnC,CAAC;AAKF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,qBAAqB,EAAE,iBAuCnC,CAAC;AAgEF,2EAA2E;AAC3E,qBAAa,cAAe,SAAQ,KAAK;gBAC3B,OAAO,EAAE,MAAM;CAI5B;AAED,eAAO,MAAM,UAAU,EAAE,SAAS,iBAAiB,EAGlD,CAAC"}
@@ -0,0 +1,208 @@
1
+ // How the provider gets into a host app, one strategy per install path.
2
+ //
3
+ // The spec asks for this explicitly: another framework, or another way of
4
+ // mounting into Next.js, should be a new strategy rather than a rewrite of the
5
+ // injector. So the orchestration in `init.ts` knows only this interface, and
6
+ // the one strategy that exists here knows only App Router layouts.
7
+ import { joinPath } from "./fs.js";
8
+ /**
9
+ * The package `init` writes into a host app's dependencies and imports from in
10
+ * their layout.
11
+ *
12
+ * It is the name of a DIFFERENT package from this one, and that is the point:
13
+ * it is data this installer writes into somebody else's repository, never a
14
+ * module specifier. Nothing here imports the toolbar, and nothing here may -
15
+ * an emitted `@bitvea/*` specifier is what would stop this tarball standing on
16
+ * its own with no node_modules at all.
17
+ */
18
+ export const PACKAGE_NAME = "@bitvea/feedback-toolbar";
19
+ export const COMPONENT_NAME = "FeedbackToolbar";
20
+ /** The file the script-tag route is served as, relative to the backend URL. */
21
+ export const SCRIPT_BUNDLE_PATH = "/toolbar.js";
22
+ /**
23
+ * The environment a script-tag install declares, when the developer has not
24
+ * said otherwise.
25
+ *
26
+ * The gate treats "absent" as off, and a static HTML file cannot know which
27
+ * deployment served it - so a tag with no `data-environment` mounts nothing,
28
+ * ever. That is the correct default for a value we could only guess, and the
29
+ * wrong default for an installer whose whole job is to leave a working
30
+ * install behind: #17 asks for a toolbar that mounts on a plain page with no
31
+ * build step, and a tag the developer has to hand-edit is not that.
32
+ *
33
+ * So `init` writes an environment, and the value is a review environment
34
+ * because installing a review tool on a page is what the command means. The
35
+ * result is printed as a change and called out as a note, and `--environment`
36
+ * overrides it - including with `production`, which is how somebody turns the
37
+ * tag off without removing it.
38
+ */
39
+ export const DEFAULT_SCRIPT_ENVIRONMENT = "preview";
40
+ /** Where an App Router root layout lives, most conventional first. */
41
+ const APP_ROUTER_LAYOUTS = [
42
+ "app/layout.tsx",
43
+ "src/app/layout.tsx",
44
+ "app/layout.jsx",
45
+ "src/app/layout.jsx",
46
+ ];
47
+ export const nextAppRouterStrategy = {
48
+ id: "next-app-router",
49
+ label: "Next.js App Router",
50
+ delivery: "npm-package",
51
+ detect(fs, root) {
52
+ for (const candidate of APP_ROUTER_LAYOUTS) {
53
+ const path = joinPath(root, candidate);
54
+ if (fs.exists(path)) {
55
+ return { path, description: `root layout at ${candidate}` };
56
+ }
57
+ }
58
+ return null;
59
+ },
60
+ isInstalled(source) {
61
+ // Both halves, because a half-installed file is what a failed earlier run
62
+ // would leave and re-running should finish it rather than skip it.
63
+ return (source.includes(PACKAGE_NAME) && source.includes(`<${COMPONENT_NAME}`));
64
+ },
65
+ inject(source, context) {
66
+ let out = source;
67
+ if (!out.includes(PACKAGE_NAME)) {
68
+ out = addImport(out);
69
+ }
70
+ if (!out.includes(`<${COMPONENT_NAME}`)) {
71
+ // The API URL goes in the MARKUP, not only into an env file, and that is
72
+ // the difference between an install that works after a deploy and one
73
+ // that silently does nothing.
74
+ //
75
+ // `init` writes `NEXT_PUBLIC_FEEDBACK_TOOLBAR_API_URL` into `.env.local`,
76
+ // which create-next-app gitignores - so on the host's next deploy the
77
+ // variable does not exist, `resolveConfig` leaves `enabled` false, and
78
+ // the deployed toolbar renders nothing. The developer sees no toolbar and
79
+ // no error, and nothing in the install told them to add the variable to
80
+ // their hosting provider by hand.
81
+ //
82
+ // The value is not a secret - it is a `NEXT_PUBLIC_` URL, and the
83
+ // script-tag route already writes the same value into a page - so
84
+ // committing it is exactly right. `resolveConfig` lets a prop beat the
85
+ // environment, which is what makes this deterministic; a developer who
86
+ // wants a different backend per environment deletes the prop and sets
87
+ // the variable in their hosting provider instead.
88
+ out = insertBeforeClosingBody(out, `<${COMPONENT_NAME} apiUrl="${context.apiUrl}" />`);
89
+ }
90
+ return out;
91
+ },
92
+ };
93
+ /** Where a plain HTML entry point lives, most conventional first. */
94
+ const HTML_ENTRY_FILES = ["index.html", "public/index.html", "src/index.html"];
95
+ /**
96
+ * The `<script src>` install, for an app with no build step to add a
97
+ * dependency to. Second in the list on purpose: an App Router app usually
98
+ * also has an index.html somewhere, and the npm install is the better one.
99
+ *
100
+ * Note what the injected tag does NOT carry: a deploy key. There is no
101
+ * supported way to put one in a page, and the bundle refuses to mount when it
102
+ * finds anything that looks like one - a key in a page is a key in every
103
+ * visitor's hands, and its whole power is creating projects.
104
+ *
105
+ * It does carry an environment, and it has to. A static file cannot know which
106
+ * deployment served it and the gate treats absent as off, so a tag without one
107
+ * mounts nothing at all - which is what #17's "mounts a working toolbar on a
108
+ * plain HTML page" cannot mean. The value is written, printed as a change, and
109
+ * called out as a note; `--environment production` is how a developer turns
110
+ * the tag off without deleting it. See DEFAULT_SCRIPT_ENVIRONMENT.
111
+ */
112
+ export const htmlScriptTagStrategy = {
113
+ id: "html-script-tag",
114
+ label: "plain HTML page",
115
+ delivery: "script-tag",
116
+ detect(fs, root) {
117
+ for (const candidate of HTML_ENTRY_FILES) {
118
+ const path = joinPath(root, candidate);
119
+ if (fs.exists(path)) {
120
+ return { path, description: `HTML page at ${candidate}` };
121
+ }
122
+ }
123
+ return null;
124
+ },
125
+ isInstalled(source) {
126
+ return source.includes(SCRIPT_BUNDLE_PATH);
127
+ },
128
+ inject(source, context) {
129
+ if (!context.projectUrl) {
130
+ // Unlike the npm route, this one cannot fall back to reading the URL
131
+ // from the environment at runtime: a static page has no environment.
132
+ // Without it the toolbar would ask the backend about a project keyed on
133
+ // the empty string, so refuse rather than install something inert.
134
+ throw new InjectionError("a script-tag install needs the project's URL, and nothing here reports one.\n" +
135
+ "Re-run with --project-url https://myapp.com.");
136
+ }
137
+ const environment = context.environment ?? DEFAULT_SCRIPT_ENVIRONMENT;
138
+ return insertBeforeClosingBody(source, `<script src="${context.apiUrl}${SCRIPT_BUNDLE_PATH}" ` +
139
+ `data-api-url="${context.apiUrl}" ` +
140
+ `data-project-url="${context.projectUrl}" ` +
141
+ `data-environment="${environment}" defer></script>`);
142
+ },
143
+ };
144
+ function addImport(source) {
145
+ const importLine = `import { ${COMPONENT_NAME} } from "${PACKAGE_NAME}";\n`;
146
+ // After the last top-level import, so the file keeps its import block
147
+ // together rather than growing a stray line above a directive.
148
+ const importRe = /^import[\s\S]*?;[ \t]*$/gm;
149
+ let lastEnd = -1;
150
+ for (const match of source.matchAll(importRe)) {
151
+ lastEnd = match.index + match[0].length;
152
+ }
153
+ if (lastEnd >= 0) {
154
+ return `${source.slice(0, lastEnd)}\n${importLine.trimEnd()}${source.slice(lastEnd)}`;
155
+ }
156
+ // No imports at all: go after a leading "use client" directive if present,
157
+ // since nothing may precede one.
158
+ const directive = /^\s*("use client"|'use client');?\s*\n/.exec(source);
159
+ if (directive) {
160
+ const end = directive.index + directive[0].length;
161
+ return `${source.slice(0, end)}\n${importLine}${source.slice(end)}`;
162
+ }
163
+ return `${importLine}\n${source}`;
164
+ }
165
+ /**
166
+ * Put `markup` immediately inside the closing `</body>`.
167
+ *
168
+ * Shared by both strategies because the placement problem is the same one in
169
+ * JSX and in HTML, and the interesting case is the same too: the one-line
170
+ * `<body>…</body>` that Next's template and hand-written HTML pages both use.
171
+ */
172
+ function insertBeforeClosingBody(source, markup) {
173
+ const closingBody = source.lastIndexOf("</body>");
174
+ if (closingBody === -1) {
175
+ // Refusing beats guessing. A layout with no <body> is not an App Router
176
+ // root layout, and injecting somewhere plausible would produce a build
177
+ // failure the developer has to reverse-engineer.
178
+ throw new InjectionError("found a layout file but no <body> tag to place the toolbar in");
179
+ }
180
+ // Immediately before `</body>`, never at the start of its line: Next's own
181
+ // template ships `<body>{children}</body>` on one line, and inserting at the
182
+ // line start would put the toolbar between <html> and <body>, which is a
183
+ // broken layout rather than an install.
184
+ const lineStart = source.lastIndexOf("\n", closingBody) + 1;
185
+ const beforeOnLine = source.slice(lineStart, closingBody);
186
+ const openingBodyOnSameLine = beforeOnLine.includes("<body");
187
+ if (openingBodyOnSameLine) {
188
+ // Everything is on one line, so splice in place rather than reformat.
189
+ return `${source.slice(0, closingBody)}${markup}${source.slice(closingBody)}`;
190
+ }
191
+ // `</body>` is on its own line: take its indentation plus one step, so the
192
+ // diff reads as if a person wrote it.
193
+ const indent = beforeOnLine.match(/^\s*/)?.[0] ?? "";
194
+ const line = `${indent} ${markup}\n`;
195
+ return `${source.slice(0, lineStart)}${line}${source.slice(lineStart)}`;
196
+ }
197
+ /** A failure the developer can act on, as opposed to an internal error. */
198
+ export class InjectionError extends Error {
199
+ constructor(message) {
200
+ super(message);
201
+ this.name = "InjectionError";
202
+ }
203
+ }
204
+ export const STRATEGIES = [
205
+ nextAppRouterStrategy,
206
+ htmlScriptTagStrategy,
207
+ ];
208
+ //# sourceMappingURL=strategies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strategies.js","sourceRoot":"","sources":["../../src/install/strategies.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /** What a build environment can tell the backend about the deployment. */
2
+ export interface RegisterProjectRequest {
3
+ /** Base URL of the BitVea Feedback backend. */
4
+ apiUrl: string;
5
+ /** Deploy key. Read from a flag or the environment, never from a file. */
6
+ apiKey: string;
7
+ /** The deployment URL to register, exactly as the caller resolved it. */
8
+ projectUrl: string;
9
+ repoOwner?: string | undefined;
10
+ repoSlug?: string | undefined;
11
+ packageName?: string | undefined;
12
+ /**
13
+ * Browser origins this deployment serves the toolbar from, which is not the
14
+ * same question as `projectUrl` and is why both are sent. See
15
+ * `inferProjectOrigins`.
16
+ */
17
+ origins?: string[] | undefined;
18
+ }
19
+ /**
20
+ * `created` is the whole answer: false means the URL was already registered,
21
+ * which is a success, not a conflict. `project` is what the backend named it,
22
+ * so the CLI can echo something recognisable back.
23
+ */
24
+ export interface RegisterProjectResponse {
25
+ created: boolean;
26
+ project?: {
27
+ id: string;
28
+ name: string;
29
+ slug: string;
30
+ url: string | null;
31
+ };
32
+ }
33
+ export type RegisterProjectTransport = (input: RegisterProjectRequest) => Promise<RegisterProjectResponse>;
34
+ /**
35
+ * A failure the person running the command can act on, as opposed to a bug.
36
+ * The CLI prints `message` and exits; nothing else is expected to catch it.
37
+ */
38
+ export declare class RegistrationError extends Error {
39
+ constructor(message: string);
40
+ }
41
+ /** The real transport. Injected everywhere else so tests stay off the network. */
42
+ export declare const postRegistration: RegisterProjectTransport;
43
+ //# sourceMappingURL=registration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registration.d.ts","sourceRoot":"","sources":["../src/registration.ts"],"names":[],"mappings":"AAcA,0EAA0E;AAC1E,MAAM,WAAW,sBAAsB;IACrC,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;CAChC;AAED;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;CAC1E;AAED,MAAM,MAAM,wBAAwB,GAAG,CACrC,KAAK,EAAE,sBAAsB,KAC1B,OAAO,CAAC,uBAAuB,CAAC,CAAC;AAEtC;;;GAGG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM;CAI5B;AAED,kFAAkF;AAClF,eAAO,MAAM,gBAAgB,EAAE,wBAwE9B,CAAC"}