@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
|
@@ -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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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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 @@
|
|
|
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"}
|