@omg-dev/vite-plugin 0.4.29 → 0.4.31
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/dist/index.mjs +12 -47
- package/package.json +3 -3
- package/src/brand-badge-inject.test.ts +1 -14
- package/src/index.ts +2 -111
- package/src/feedback-inject.test.ts +0 -79
package/dist/index.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import fs from "node:fs";
|
|
|
3
3
|
import { schemaToCollections, schemaToDrizzle, schemaToZod } from "@omg-dev/schema";
|
|
4
4
|
import { scanTriggers } from "@omg-dev/server/trigger-scan";
|
|
5
5
|
//#region src/auth-bridge.ts
|
|
6
|
-
const AUTH_RELAY_SKIP_HEADERS = new Set([
|
|
6
|
+
const AUTH_RELAY_SKIP_HEADERS = /* @__PURE__ */ new Set([
|
|
7
7
|
"set-cookie",
|
|
8
8
|
"content-encoding",
|
|
9
9
|
"content-length",
|
|
@@ -48,7 +48,7 @@ async function fetchAuthUpstream(kind, init, qs = "") {
|
|
|
48
48
|
}
|
|
49
49
|
//#endregion
|
|
50
50
|
//#region src/scanner.ts
|
|
51
|
-
const HTTP_METHODS = new Set([
|
|
51
|
+
const HTTP_METHODS = /* @__PURE__ */ new Set([
|
|
52
52
|
"GET",
|
|
53
53
|
"POST",
|
|
54
54
|
"PUT",
|
|
@@ -180,6 +180,8 @@ function extractExports(source) {
|
|
|
180
180
|
const EXPORT_WORKFLOW_RE = /^export\s+const\s+(\w+)\s*(?::\s*[^=]+)?\s*=\s*workflow\s*\(\s*(.+?)\s*,/gm;
|
|
181
181
|
const NAME_RE = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
182
182
|
var WorkflowScanError = class extends Error {
|
|
183
|
+
file;
|
|
184
|
+
detail;
|
|
183
185
|
constructor(file, detail) {
|
|
184
186
|
super(`[vibes:workflows] ${file}: ${detail}`);
|
|
185
187
|
this.file = file;
|
|
@@ -457,29 +459,6 @@ function isAppEntry(id, root) {
|
|
|
457
459
|
}
|
|
458
460
|
//#endregion
|
|
459
461
|
//#region src/index.ts
|
|
460
|
-
async function pwaDeclaresSubpathExport(resolve, importer, subpath) {
|
|
461
|
-
try {
|
|
462
|
-
const rootEntry = await resolve("@omg-dev/pwa", importer);
|
|
463
|
-
if (!rootEntry?.id) return false;
|
|
464
|
-
let dir = path.dirname(rootEntry.id.split("?")[0]);
|
|
465
|
-
for (let i = 0; i < 12; i++) {
|
|
466
|
-
const pkgPath = path.join(dir, "package.json");
|
|
467
|
-
if (fs.existsSync(pkgPath)) try {
|
|
468
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
469
|
-
if (pkg.name === "@omg-dev/pwa") {
|
|
470
|
-
const exp = pkg.exports;
|
|
471
|
-
return !!exp && typeof exp === "object" && subpath in exp;
|
|
472
|
-
}
|
|
473
|
-
} catch {}
|
|
474
|
-
const parent = path.dirname(dir);
|
|
475
|
-
if (parent === dir) break;
|
|
476
|
-
dir = parent;
|
|
477
|
-
}
|
|
478
|
-
return false;
|
|
479
|
-
} catch {
|
|
480
|
-
return false;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
462
|
function vibes(opts = {}) {
|
|
484
463
|
let resolvedRoot;
|
|
485
464
|
let config;
|
|
@@ -487,9 +466,6 @@ function vibes(opts = {}) {
|
|
|
487
466
|
const pwaEnabled = opts.pwa !== false;
|
|
488
467
|
const pwaOpts = typeof opts.pwa === "object" ? opts.pwa : {};
|
|
489
468
|
let pwaAutoInjected = false;
|
|
490
|
-
let remixCtaInjected = false;
|
|
491
|
-
const feedbackEnabled = opts.feedback !== false;
|
|
492
|
-
let feedbackInjected = false;
|
|
493
469
|
const brandBadgeEnabled = opts.brandBadge !== false;
|
|
494
470
|
let brandBadgeInjected = false;
|
|
495
471
|
function existingIconFiles() {
|
|
@@ -508,10 +484,7 @@ function vibes(opts = {}) {
|
|
|
508
484
|
name: "vibes",
|
|
509
485
|
enforce: "pre",
|
|
510
486
|
config() {
|
|
511
|
-
return {
|
|
512
|
-
define: { __VIBES_BRAND_FEEDBACK__: JSON.stringify(brandBadgeEnabled && feedbackEnabled) },
|
|
513
|
-
server: { watch: { ignored: ["**/.vibes/**"] } }
|
|
514
|
-
};
|
|
487
|
+
return { server: { watch: { ignored: ["**/.vibes/**"] } } };
|
|
515
488
|
},
|
|
516
489
|
configResolved(resolvedConfig) {
|
|
517
490
|
config = resolvedConfig;
|
|
@@ -795,7 +768,8 @@ window.__VIBES_AUTH_SESSION_URL = "/__vibes/auth/session";
|
|
|
795
768
|
if (bearerProto) {
|
|
796
769
|
const token = bearerProto.slice(13);
|
|
797
770
|
try {
|
|
798
|
-
|
|
771
|
+
const fakeReq = new Request("http://localhost/__vibes_sub", { headers: { authorization: `Bearer ${token}` } });
|
|
772
|
+
userId = (await subAuthMW(fakeReq))?.userId ?? null;
|
|
799
773
|
} catch {}
|
|
800
774
|
}
|
|
801
775
|
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
@@ -810,7 +784,8 @@ window.__VIBES_AUTH_SESSION_URL = "/__vibes/auth/session";
|
|
|
810
784
|
};
|
|
811
785
|
addSubClient(subClient);
|
|
812
786
|
ws.on("message", (data) => {
|
|
813
|
-
|
|
787
|
+
const raw = typeof data === "string" ? data : Buffer.isBuffer(data) ? data.toString("utf8") : Array.isArray(data) ? Buffer.concat(data).toString("utf8") : Buffer.from(data).toString("utf8");
|
|
788
|
+
handleSubMessage(subClient, raw);
|
|
814
789
|
});
|
|
815
790
|
const closeOut = () => {
|
|
816
791
|
if (subClient.readyState === 3) return;
|
|
@@ -1014,7 +989,7 @@ window.__VIBES_AUTH_SESSION_URL = "/__vibes/auth/session";
|
|
|
1014
989
|
},
|
|
1015
990
|
async transform(code, id) {
|
|
1016
991
|
if (config?.command !== "build") return null;
|
|
1017
|
-
if (!pwaEnabled && !
|
|
992
|
+
if (!pwaEnabled && !brandBadgeEnabled) return null;
|
|
1018
993
|
if (!isAppEntry(id, resolvedRoot)) return null;
|
|
1019
994
|
const imports = [];
|
|
1020
995
|
if (brandBadgeEnabled && !brandBadgeInjected) if (!await this.resolve("@omg-dev/sdk/brand/auto", id)) this.warn("[vibes] brand badge: @omg-dev/sdk/brand/auto is not available — badge skipped. Update @omg-dev/sdk, or silence this with vibes({ brandBadge: false }).");
|
|
@@ -1022,22 +997,11 @@ window.__VIBES_AUTH_SESSION_URL = "/__vibes/auth/session";
|
|
|
1022
997
|
brandBadgeInjected = true;
|
|
1023
998
|
imports.push(`import "@omg-dev/sdk/brand/auto";`);
|
|
1024
999
|
}
|
|
1025
|
-
if (feedbackEnabled && !brandBadgeEnabled && !feedbackInjected) if (!await this.resolve("@omg-dev/sdk/feedback/auto", id)) this.warn("[vibes] feedback: @omg-dev/sdk is not installed — shake-to-report skipped. Add @omg-dev/sdk to dependencies, or silence this with vibes({ feedback: false }).");
|
|
1026
|
-
else {
|
|
1027
|
-
feedbackInjected = true;
|
|
1028
|
-
imports.push(`import "@omg-dev/sdk/feedback/auto";`);
|
|
1029
|
-
}
|
|
1030
1000
|
if (pwaEnabled && pwaOpts.autoPrompt !== false && !pwaAutoInjected) if (!await this.resolve("@omg-dev/pwa/auto", id)) this.warn("[vibes] pwa: @omg-dev/pwa is not installed — soft install prompt skipped. Add @omg-dev/pwa to dependencies, or silence this with vibes({ pwa: { autoPrompt: false } }).");
|
|
1031
1001
|
else {
|
|
1032
1002
|
pwaAutoInjected = true;
|
|
1033
1003
|
imports.push(`import "@omg-dev/pwa/auto";`);
|
|
1034
1004
|
}
|
|
1035
|
-
if (pwaEnabled && !brandBadgeEnabled && !remixCtaInjected) {
|
|
1036
|
-
if (await pwaDeclaresSubpathExport((source, importer) => this.resolve(source, importer), id, "./remix-cta")) {
|
|
1037
|
-
remixCtaInjected = true;
|
|
1038
|
-
imports.push(`import "@omg-dev/pwa/remix-cta";`);
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
1005
|
if (imports.length === 0) return null;
|
|
1042
1006
|
return {
|
|
1043
1007
|
code: `${code}\n${imports.join("\n")}\n`,
|
|
@@ -1048,7 +1012,8 @@ window.__VIBES_AUTH_SESSION_URL = "/__vibes/auth/session";
|
|
|
1048
1012
|
if (!pwaEnabled) return;
|
|
1049
1013
|
if (userManifestHref()) return;
|
|
1050
1014
|
const indexPath = path.join(resolvedRoot, "index.html");
|
|
1051
|
-
const
|
|
1015
|
+
const indexHtml = fs.existsSync(indexPath) ? fs.readFileSync(indexPath, "utf-8") : "";
|
|
1016
|
+
const cfg = resolvePwaConfig(pwaOpts, indexHtml);
|
|
1052
1017
|
this.emitFile({
|
|
1053
1018
|
type: "asset",
|
|
1054
1019
|
fileName: MANIFEST_FILE,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omg-dev/vite-plugin",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.31",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"build": "vp pack src/index.ts --no-fail-on-warn"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@omg-dev/server": "0.4.
|
|
19
|
-
"@omg-dev/schema": "0.4.
|
|
18
|
+
"@omg-dev/server": "0.4.31",
|
|
19
|
+
"@omg-dev/schema": "0.4.31",
|
|
20
20
|
"ws": "^8.18.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
@@ -29,7 +29,7 @@ async function runTransform(
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
function buildPlugin(opts?: Parameters<typeof vibes>[0]) {
|
|
32
|
-
const plugin = vibes({ root: ROOT, pwa: false,
|
|
32
|
+
const plugin = vibes({ root: ROOT, pwa: false, ...opts })
|
|
33
33
|
const configResolved = plugin.configResolved as (cfg: unknown) => void
|
|
34
34
|
configResolved({ command: "build", root: ROOT })
|
|
35
35
|
return plugin
|
|
@@ -71,19 +71,6 @@ describe("brand badge injection", () => {
|
|
|
71
71
|
expect(context.warnings.some((w) => w.includes("brand badge"))).toBe(true)
|
|
72
72
|
})
|
|
73
73
|
|
|
74
|
-
test("badge ON bundles feedback + remix: no standalone imports injected", async () => {
|
|
75
|
-
// brand badge default ON, with feedback + pwa also on. The badge carries
|
|
76
|
-
// feedback (button-less) and the "Make it mine" CTA itself, so the
|
|
77
|
-
// standalone feedback/auto + remix-cta imports must NOT be appended.
|
|
78
|
-
const plugin = vibes({ root: ROOT })
|
|
79
|
-
const configResolved = plugin.configResolved as (cfg: unknown) => void
|
|
80
|
-
configResolved({ command: "build", root: ROOT })
|
|
81
|
-
const out = await runTransform(plugin, "export default 1", ENTRY, ctx())
|
|
82
|
-
expect(out?.code).toContain(BRAND_IMPORT)
|
|
83
|
-
expect(out?.code).not.toContain(`import "@omg-dev/sdk/feedback/auto";`)
|
|
84
|
-
expect(out?.code).not.toContain(`import "@omg-dev/pwa/remix-cta";`)
|
|
85
|
-
})
|
|
86
|
-
|
|
87
74
|
test("serve mode never injects the badge", async () => {
|
|
88
75
|
const plugin = vibes({ root: ROOT })
|
|
89
76
|
const configResolved = plugin.configResolved as (cfg: unknown) => void
|
package/src/index.ts
CHANGED
|
@@ -34,59 +34,12 @@ export interface VibesOptions {
|
|
|
34
34
|
* install prompt. Default ON; pass `false` to opt out, or an options
|
|
35
35
|
* object to customize (see PwaOptions). */
|
|
36
36
|
pwa?: boolean | PwaOptions
|
|
37
|
-
/** Shake-to-report feedback widget for published builds — auto-mounts
|
|
38
|
-
* <VibesFeedback/> outside the app's React tree (shake / two-finger press
|
|
39
|
-
* summons it; report → agent /_report → LFG triage). Default ON; pass
|
|
40
|
-
* `false` to opt out and place <VibesFeedback/> from @omg-dev/sdk/feedback
|
|
41
|
-
* manually. */
|
|
42
|
-
feedback?: boolean
|
|
43
37
|
/** Small "omg" badge for published apps. Auto-mounts outside the app's React
|
|
44
38
|
* tree and opens a built-in "What is omg?" dialog. Default ON; pass `false`
|
|
45
39
|
* to omit it for a specific app while this rolls out. */
|
|
46
40
|
brandBadge?: boolean
|
|
47
41
|
}
|
|
48
42
|
|
|
49
|
-
// Resolve @omg-dev/pwa for `importer` and report whether its package.json declares
|
|
50
|
-
// the given subpath (e.g. "./remix-cta") in its `exports` map. Used to keep the
|
|
51
|
-
// build-time CTA injection skew-safe: an older baked @omg-dev/pwa that predates a
|
|
52
|
-
// subpath export must NOT get the import appended, or the bundle fails to resolve.
|
|
53
|
-
// Returns false on any resolution / parse hiccup — the CTA is additive, so when
|
|
54
|
-
// in doubt we skip it rather than risk breaking the user's build.
|
|
55
|
-
async function pwaDeclaresSubpathExport(
|
|
56
|
-
resolve: (source: string, importer: string) => Promise<{ id: string } | null>,
|
|
57
|
-
importer: string,
|
|
58
|
-
subpath: string,
|
|
59
|
-
): Promise<boolean> {
|
|
60
|
-
try {
|
|
61
|
-
// Resolve the always-present root export, then walk up to the owning
|
|
62
|
-
// package.json. We can't resolve "@omg-dev/pwa/package.json" directly because
|
|
63
|
-
// an older exports map may not expose "./package.json".
|
|
64
|
-
const rootEntry = await resolve("@omg-dev/pwa", importer)
|
|
65
|
-
if (!rootEntry?.id) return false
|
|
66
|
-
let dir = path.dirname(rootEntry.id.split("?")[0])
|
|
67
|
-
for (let i = 0; i < 12; i++) {
|
|
68
|
-
const pkgPath = path.join(dir, "package.json")
|
|
69
|
-
if (fs.existsSync(pkgPath)) {
|
|
70
|
-
try {
|
|
71
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"))
|
|
72
|
-
if (pkg.name === "@omg-dev/pwa") {
|
|
73
|
-
const exp = pkg.exports
|
|
74
|
-
return !!exp && typeof exp === "object" && subpath in exp
|
|
75
|
-
}
|
|
76
|
-
} catch {
|
|
77
|
-
// Unreadable/!JSON package.json — keep climbing toward the real one.
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
const parent = path.dirname(dir)
|
|
81
|
-
if (parent === dir) break
|
|
82
|
-
dir = parent
|
|
83
|
-
}
|
|
84
|
-
return false
|
|
85
|
-
} catch {
|
|
86
|
-
return false
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
43
|
// ── Plugin ────────────────────────────────────────────────────────────────────
|
|
91
44
|
|
|
92
45
|
export default function vibes(opts: VibesOptions = {}): Plugin {
|
|
@@ -97,10 +50,7 @@ export default function vibes(opts: VibesOptions = {}): Plugin {
|
|
|
97
50
|
const pwaEnabled = opts.pwa !== false
|
|
98
51
|
const pwaOpts: PwaOptions = typeof opts.pwa === "object" ? opts.pwa : {}
|
|
99
52
|
let pwaAutoInjected = false
|
|
100
|
-
let remixCtaInjected = false
|
|
101
53
|
|
|
102
|
-
const feedbackEnabled = opts.feedback !== false
|
|
103
|
-
let feedbackInjected = false
|
|
104
54
|
const brandBadgeEnabled = opts.brandBadge !== false
|
|
105
55
|
let brandBadgeInjected = false
|
|
106
56
|
|
|
@@ -137,13 +87,6 @@ export default function vibes(opts: VibesOptions = {}): Plugin {
|
|
|
137
87
|
// browser, so blanket-ignore it.
|
|
138
88
|
config() {
|
|
139
89
|
return {
|
|
140
|
-
// When the brand badge is on (default), it bundles the feedback sheet
|
|
141
|
-
// (no separate floating button). Expose whether feedback is actually
|
|
142
|
-
// enabled so the badge can omit it when the app opts out via
|
|
143
|
-
// vibes({ feedback: false }).
|
|
144
|
-
define: {
|
|
145
|
-
__VIBES_BRAND_FEEDBACK__: JSON.stringify(brandBadgeEnabled && feedbackEnabled),
|
|
146
|
-
},
|
|
147
90
|
server: {
|
|
148
91
|
watch: {
|
|
149
92
|
ignored: ["**/.vibes/**"],
|
|
@@ -813,16 +756,13 @@ window.__VIBES_AUTH_SESSION_URL = "/__vibes/auth/session";
|
|
|
813
756
|
},
|
|
814
757
|
|
|
815
758
|
// ── transform ─────────────────────────────────────────────────────────────
|
|
816
|
-
// Published builds get
|
|
759
|
+
// Published builds get platform side-effect imports appended to the app entry:
|
|
817
760
|
// - @omg-dev/pwa/auto — the soft install prompt (gated by autoPrompt)
|
|
818
|
-
// - @omg-dev/pwa/remix-cta — the "Make your own" remix bridge (always on
|
|
819
|
-
// when pwa is enabled; self-gates on the baked
|
|
820
|
-
// VITE_APP_SLUG, so it's inert if not published)
|
|
821
761
|
// Build-time injection (not template source) so both survive agent rewrites
|
|
822
762
|
// of main.tsx and never load in dev preview.
|
|
823
763
|
async transform(code: string, id: string) {
|
|
824
764
|
if (config?.command !== "build") return null
|
|
825
|
-
if (!pwaEnabled && !
|
|
765
|
+
if (!pwaEnabled && !brandBadgeEnabled) return null
|
|
826
766
|
if (!isAppEntry(id, resolvedRoot)) return null
|
|
827
767
|
|
|
828
768
|
const imports: string[] = []
|
|
@@ -843,28 +783,6 @@ window.__VIBES_AUTH_SESSION_URL = "/__vibes/auth/session";
|
|
|
843
783
|
}
|
|
844
784
|
}
|
|
845
785
|
|
|
846
|
-
// Shake-to-report feedback widget — opt-out via vibes({ feedback: false }).
|
|
847
|
-
// Build-only + outside the React tree, so it survives agent rewrites of
|
|
848
|
-
// main.tsx and never arms a gesture listener in the dev preview iframe.
|
|
849
|
-
//
|
|
850
|
-
// When the brand badge is on (default) it ALREADY mounts a button-less
|
|
851
|
-
// feedback sheet and summons it from its dialog, so injecting the
|
|
852
|
-
// standalone widget here would double-mount it (two floating controls —
|
|
853
|
-
// the exact duplication we're collapsing). Only inject standalone feedback
|
|
854
|
-
// when the brand badge is disabled.
|
|
855
|
-
if (feedbackEnabled && !brandBadgeEnabled && !feedbackInjected) {
|
|
856
|
-
const resolvedFb = await this.resolve("@omg-dev/sdk/feedback/auto", id)
|
|
857
|
-
if (!resolvedFb) {
|
|
858
|
-
this.warn(
|
|
859
|
-
"[vibes] feedback: @omg-dev/sdk is not installed — shake-to-report skipped. " +
|
|
860
|
-
"Add @omg-dev/sdk to dependencies, or silence this with vibes({ feedback: false }).",
|
|
861
|
-
)
|
|
862
|
-
} else {
|
|
863
|
-
feedbackInjected = true
|
|
864
|
-
imports.push(`import "@omg-dev/sdk/feedback/auto";`)
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
|
|
868
786
|
// Soft install prompt — opt-out via vibes({ pwa: { autoPrompt: false } }).
|
|
869
787
|
if (pwaEnabled && pwaOpts.autoPrompt !== false && !pwaAutoInjected) {
|
|
870
788
|
const resolved = await this.resolve("@omg-dev/pwa/auto", id)
|
|
@@ -879,33 +797,6 @@ window.__VIBES_AUTH_SESSION_URL = "/__vibes/auth/session";
|
|
|
879
797
|
}
|
|
880
798
|
}
|
|
881
799
|
|
|
882
|
-
// "Make your own" remix CTA — independent of autoPrompt. It self-gates at
|
|
883
|
-
// runtime on import.meta.env.VITE_APP_SLUG (baked by the orchestrator on
|
|
884
|
-
// deploy), so it renders nothing on a non-published build.
|
|
885
|
-
//
|
|
886
|
-
// Skew guard: the remix-cta subpath shipped in @omg-dev/pwa >= 0.4.16. An app
|
|
887
|
-
// whose baked @omg-dev/pwa predates it (e.g. 0.4.12) has no "./remix-cta" in
|
|
888
|
-
// its exports map, so injecting the import hard-fails the build with
|
|
889
|
-
// "Errored while resolving @omg-dev/pwa/remix-cta". this.resolve() alone is not
|
|
890
|
-
// a reliable gate across this version skew, so only inject once we've
|
|
891
|
-
// confirmed the resolved package actually declares the subpath export.
|
|
892
|
-
//
|
|
893
|
-
// The brand badge (default on) now hosts "Make it mine" itself: same omg
|
|
894
|
-
// mark, remix CTA first, and it collapses to the plain branding badge on
|
|
895
|
-
// dismiss. So only inject the standalone remix-cta when the brand badge is
|
|
896
|
-
// off — otherwise we'd render two corner pills with the same intent.
|
|
897
|
-
if (pwaEnabled && !brandBadgeEnabled && !remixCtaInjected) {
|
|
898
|
-
const exported = await pwaDeclaresSubpathExport(
|
|
899
|
-
(source, importer) => this.resolve(source, importer),
|
|
900
|
-
id,
|
|
901
|
-
"./remix-cta",
|
|
902
|
-
)
|
|
903
|
-
if (exported) {
|
|
904
|
-
remixCtaInjected = true
|
|
905
|
-
imports.push(`import "@omg-dev/pwa/remix-cta";`)
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
|
|
909
800
|
if (imports.length === 0) return null
|
|
910
801
|
return { code: `${code}\n${imports.join("\n")}\n`, map: null }
|
|
911
802
|
},
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
// Verifies the build-time injection of the shake-to-report feedback widget
|
|
2
|
-
// (@omg-dev/sdk/feedback/auto) into the app entry. The brand badge bundles
|
|
3
|
-
// feedback when it's on (the default), so the STANDALONE widget only injects
|
|
4
|
-
// when the brand badge is disabled — these tests run with brandBadge:false.
|
|
5
|
-
// Opt-out via vibes({ feedback: false }), build-only, app-entry-only.
|
|
6
|
-
|
|
7
|
-
import { describe, expect, test } from "bun:test"
|
|
8
|
-
import vibes from "./index.ts"
|
|
9
|
-
|
|
10
|
-
const ROOT = "/app"
|
|
11
|
-
const ENTRY = `${ROOT}/src/main.tsx`
|
|
12
|
-
const FEEDBACK_IMPORT = `import "@omg-dev/sdk/feedback/auto";`
|
|
13
|
-
|
|
14
|
-
// Minimal Rollup plugin-context stub: resolve() succeeds for everything,
|
|
15
|
-
// warn() is captured. transform() is a plain async fn we bind `this` onto.
|
|
16
|
-
function ctx() {
|
|
17
|
-
const warnings: string[] = []
|
|
18
|
-
return {
|
|
19
|
-
warnings,
|
|
20
|
-
resolve: async (src: string) => ({ id: src }),
|
|
21
|
-
warn: (msg: string) => warnings.push(msg),
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async function runTransform(
|
|
26
|
-
plugin: ReturnType<typeof vibes>,
|
|
27
|
-
code: string,
|
|
28
|
-
id: string,
|
|
29
|
-
context: ReturnType<typeof ctx>,
|
|
30
|
-
) {
|
|
31
|
-
const fn = plugin.transform as (this: unknown, code: string, id: string) => Promise<unknown>
|
|
32
|
-
return fn.call(context, code, id) as Promise<{ code: string } | null>
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function buildPlugin(opts?: Parameters<typeof vibes>[0]) {
|
|
36
|
-
const plugin = vibes({ root: ROOT, brandBadge: false, ...opts })
|
|
37
|
-
// configResolved sets command + root that transform gates on.
|
|
38
|
-
const configResolved = plugin.configResolved as (cfg: unknown) => void
|
|
39
|
-
configResolved({ command: "build", root: ROOT })
|
|
40
|
-
return plugin
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
describe("feedback widget injection", () => {
|
|
44
|
-
test("default ON: appends the auto-mount import to the app entry", async () => {
|
|
45
|
-
const plugin = buildPlugin()
|
|
46
|
-
const out = await runTransform(plugin, "export default 1", ENTRY, ctx())
|
|
47
|
-
expect(out?.code).toContain(FEEDBACK_IMPORT)
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
test("opt-out via feedback:false omits the import", async () => {
|
|
51
|
-
const plugin = buildPlugin({ feedback: false, pwa: false })
|
|
52
|
-
const out = await runTransform(plugin, "export default 1", ENTRY, ctx())
|
|
53
|
-
// Nothing to inject → transform returns null.
|
|
54
|
-
expect(out).toBeNull()
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
test("injected exactly once across multiple entry transforms", async () => {
|
|
58
|
-
const plugin = buildPlugin({ pwa: false })
|
|
59
|
-
const first = await runTransform(plugin, "export default 1", ENTRY, ctx())
|
|
60
|
-
const second = await runTransform(plugin, "export default 2", ENTRY, ctx())
|
|
61
|
-
expect(first?.code).toContain(FEEDBACK_IMPORT)
|
|
62
|
-
expect(second).toBeNull() // already injected
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
test("non-entry modules are untouched", async () => {
|
|
66
|
-
const plugin = buildPlugin()
|
|
67
|
-
const out = await runTransform(plugin, "export const x = 1", `${ROOT}/src/util.ts`, ctx())
|
|
68
|
-
expect(out).toBeNull()
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
test("warns (and skips) when @omg-dev/sdk cannot be resolved", async () => {
|
|
72
|
-
const plugin = buildPlugin({ pwa: false })
|
|
73
|
-
const context = ctx()
|
|
74
|
-
context.resolve = async () => null // simulate missing dependency
|
|
75
|
-
const out = await runTransform(plugin, "export default 1", ENTRY, context)
|
|
76
|
-
expect(out).toBeNull()
|
|
77
|
-
expect(context.warnings.some((w) => w.includes("shake-to-report skipped"))).toBe(true)
|
|
78
|
-
})
|
|
79
|
-
})
|