better-supabase 0.2.0 → 0.4.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 +37 -33
- package/dist/base64-D2gsioCY.js +42 -0
- package/dist/claims-BAf6c7is.js +48 -0
- package/dist/cli/_format-Cax5EsM9.js +101 -0
- package/dist/cli/bin.js +39 -6
- package/dist/cli/command-CK14fP91.js +502 -0
- package/dist/cli/compare-BmIWVEui.js +9 -0
- package/dist/cli/config-D9R0o1LH.js +1895 -0
- package/dist/cli/config-SSKSTUl2.js +2 -0
- package/dist/cli/db-DOBPt660.js +52 -0
- package/dist/cli/dist-B1MvLv-M.js +129 -0
- package/dist/cli/dist-CFgGknmZ.js +1112 -0
- package/dist/cli/dist-Du7VnEUG.js +621 -0
- package/dist/cli/doctor-BOym3mbL.js +2382 -0
- package/dist/cli/env-BZ0uy8XK.js +108 -0
- package/dist/cli/env-jIFUADLY.js +511 -0
- package/dist/cli/errors-4v4DTtda.js +56 -0
- package/dist/cli/fastest-levenshtein-CFN-lNDv.js +120 -0
- package/dist/cli/gen-Ccn_p90L.js +2008 -0
- package/dist/cli/gen-D5cLCULx.js +2 -0
- package/dist/cli/index.d.ts +295 -28
- package/dist/cli/index.js +184 -3
- package/dist/cli/init-ClnxRYBF.js +550 -0
- package/dist/cli/introspect-DxqfkVIv.js +81 -0
- package/dist/cli/io-k41_z-jT.js +24 -0
- package/dist/cli/json5-ChRycquj.js +787 -0
- package/dist/cli/jsonc-BUniUpOk.js +2 -0
- package/dist/cli/jsonc-D5Fu67jw.js +45 -0
- package/dist/cli/keys-ByKHnvP9.js +83 -0
- package/dist/cli/ohash-D7bG_EOb.js +277 -0
- package/dist/cli/openapi-8WHjxiTJ.js +66 -0
- package/dist/cli/project-B_GeXyHh.js +70 -0
- package/dist/cli/prompts-BNcNfAve.js +1142 -0
- package/dist/cli/prompts-BTYQGunc.js +2 -0
- package/dist/cli/read-sets-CMa29t9a.js +971 -0
- package/dist/cli/rolldown-runtime-BPOCksWG.js +24 -0
- package/dist/cli/run-CP20LKk_.js +347 -0
- package/dist/cli/seed-5J6eu_Xg.js +98 -0
- package/dist/cli/skills-LJJIhLFR.js +137 -0
- package/dist/cli/sql-D4VbqmXi.js +199 -0
- package/dist/cli/style-C0NFdJRL.js +29 -0
- package/dist/cli/toml-DB-MNnFs.js +424 -0
- package/dist/{version-Cqq1m3UY.js → cli/version-5mW_8sY1.js} +1 -1
- package/dist/cli/yaml-CYCbJ6et.js +2252 -0
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +15 -25
- package/dist/{compiler-CZ9nIuqy.d.ts → compiler-DB3aSFOA.d.ts} +2 -2
- package/dist/config/index.d.ts +4 -3
- package/dist/config/index.js +5 -3
- package/dist/config-BpxDfnbg.js +129 -0
- package/dist/{index-DOsjFvMf.d.ts → config-C1CYcm6o.d.ts} +49 -49
- package/dist/{config-ClDXmVtY.js → config-CbvFmGD8.js} +14 -137
- package/dist/{define-BXMYD9AY.js → define-Bh0AqTik.js} +221 -120
- package/dist/{define-e3zI4_4g.d.ts → define-CRS2MRV3.d.ts} +36 -31
- package/dist/edge/index.d.ts +9 -13
- package/dist/edge/index.js +51 -25
- package/dist/{entitlements-Dng-ctuT.d.ts → entitlements-Dm-FHpNH.d.ts} +2 -2
- package/dist/env/index.js +203 -1
- package/dist/{errors-Bl3rkOzo.js → errors-L0xK41y_.js} +1 -1
- package/dist/events/index.d.ts +1 -1
- package/dist/events/index.js +139 -1
- package/dist/{events-DOt3nSO-.d.ts → events-B21UOhvP.d.ts} +4 -4
- package/dist/{executor-CGdNQ27g.d.ts → executor-B5IoUxLE.d.ts} +2 -2
- package/dist/{executor-DeppvirK.js → executor-CVt6dD0B.js} +9 -4
- package/dist/{executor-BYXQ9AGn.d.ts → executor-CZ7ImRiD.d.ts} +2 -2
- package/dist/hono/index.d.ts +17 -24
- package/dist/hono/index.js +11 -11
- package/dist/{index-DuP14VHo.d.ts → index-528Ugmyp.d.ts} +30 -10
- package/dist/{index-Bw1bwtC0.d.ts → index-BAy4tsAP.d.ts} +28 -15
- package/dist/{index-CBDufH1M.d.ts → index-BBdvo-5d.d.ts} +24 -26
- package/dist/{index-DbxFr7XG.d.ts → index-BhHEC04r.d.ts} +9 -4
- package/dist/index-Cf-D2jk1.d.ts +50 -0
- package/dist/{index-Ciu5Xlml.d.ts → index-D5IfUIDh.d.ts} +6 -5
- package/dist/{index-K-3WM2Hp.d.ts → index-DFmOpBGC.d.ts} +13 -13
- package/dist/{index-D4v7sncu.d.ts → index-RlLXd8dL.d.ts} +13 -13
- package/dist/index.d.ts +17 -17
- package/dist/index.js +23 -14
- package/dist/{invalidate-DbxuL2jL.js → invalidate-CgxmRamI.js} +7 -1
- package/dist/jobs/index.d.ts +33 -26
- package/dist/jobs/index.js +44 -22
- package/dist/{json-schema-BpYqkCKf.js → json-schema-DP6V7cbr.js} +8 -3
- package/dist/lint/index.js +1 -1
- package/dist/list/index.d.ts +2 -2
- package/dist/list/index.js +131 -96
- package/dist/{live-DJyZMD6w.d.ts → live-BHakSWWo.d.ts} +26 -16
- package/dist/{live-D8rV1Ldw.js → live-CdJBOc4f.js} +13 -9
- package/dist/lookup-BaP5bI27.js +18 -0
- package/dist/mcp/index.d.ts +34 -27
- package/dist/mcp/index.js +59 -25
- package/dist/{mfa-TVia554c.d.ts → mfa-C7MNQ1Ob.d.ts} +1 -1
- package/dist/{mfa-CpLfJSde.js → mfa-byfjruR9.js} +190 -46
- package/dist/next/image/index.js +6 -3
- package/dist/next/index.d.ts +64 -48
- package/dist/next/index.js +63 -50
- package/dist/openapi/index.d.ts +1 -1
- package/dist/openapi/index.js +61 -38
- package/dist/orpc/index.d.ts +18 -21
- package/dist/orpc/index.js +12 -12
- package/dist/otel/index.d.ts +5 -5
- package/dist/otel/index.js +84 -63
- package/dist/{permdock-sql-CghlHPuH.js → permdock-sql-C4KFzLc9.js} +12 -7
- package/dist/permdock-sql-DImJULY-.d.ts +17 -0
- package/dist/{plugin-C4TR9J0Z.d.ts → plugin-bNhvJwRh.d.ts} +5 -4
- package/dist/plugins/actor/index.d.ts +1 -1
- package/dist/plugins/actor/index.js +1 -1
- package/dist/plugins/rules/index.d.ts +2 -2
- package/dist/plugins/rules/index.js +46 -16
- package/dist/plugins/soft-delete/index.d.ts +3 -3
- package/dist/plugins/soft-delete/index.js +4 -4
- package/dist/plugins/tenant/index.d.ts +3 -3
- package/dist/plugins/tenant/index.js +10 -4
- package/dist/plugins/timestamps/index.d.ts +1 -1
- package/dist/plugins/timestamps/index.js +2 -2
- package/dist/plugins/validation/index.d.ts +2 -2
- package/dist/plugins/validation/index.js +3 -3
- package/dist/pool-DFwEQwmN.d.ts +78 -0
- package/dist/postgres/index.d.ts +10 -5
- package/dist/postgres/index.js +37 -16
- package/dist/{postgrest-cAEQ_IQN.d.ts → postgrest-Bc-0Xw62.d.ts} +1 -1
- package/dist/{problem-BTkTtRV9.js → problem-BihjPR0t.js} +11 -5
- package/dist/{problem-DlgB0lYl.d.ts → problem-U8UEKccv.d.ts} +6 -4
- package/dist/query/index.d.ts +2 -2
- package/dist/query/index.js +2 -2
- package/dist/{query-ChvDbsDE.js → query-BMMSWbI7.js} +19 -19
- package/dist/react/index.d.ts +7 -7
- package/dist/react/index.js +2 -2
- package/dist/react/server.d.ts +6 -6
- package/dist/react/server.js +3 -3
- package/dist/react/session.d.ts +21 -2
- package/dist/react/session.js +19 -1
- package/dist/{react-C83BdBGt.js → react-CQHMaESR.js} +59 -55
- package/dist/{read-set-4lI3W_gZ.js → read-set-FWK8qQ58.js} +51 -16
- package/dist/realtime/index.d.ts +4 -3
- package/dist/realtime/index.js +19 -17
- package/dist/{index-CG69s15v.d.ts → refresh-BwYkyNOQ.d.ts} +7 -3
- package/dist/{resolve-CjHILk2w.d.ts → resolve-BAqQcwMW.d.ts} +17 -11
- package/dist/{resource-CgZa3YG_.d.ts → resource-BJcaIfxi.d.ts} +17 -8
- package/dist/{resource-BHEWmQJK.js → resource-My_y-Syh.js} +43 -21
- package/dist/{respond-Dne1ARSa.d.ts → respond-DgIbdrt8.d.ts} +39 -14
- package/dist/{result-DWXatkd6.js → result-Bj6yt33D.js} +2 -2
- package/dist/{result-CTkCZ6JA.d.ts → result-CqEsbgYs.d.ts} +16 -11
- package/dist/server/index.d.ts +38 -13
- package/dist/server/index.js +43 -14
- package/dist/{respond-DMcbQwfN.js → server-BA75B0mT.js} +151 -96
- package/dist/{shared-_6-uPL7E.js → shared-DnoCdMDr.js} +4 -3
- package/dist/{simplify-bwJpLnaS.js → simplify-Cqvfr9dj.js} +76 -39
- package/dist/sql/index.d.ts +139 -0
- package/dist/sql/index.js +1983 -0
- package/dist/{sql-DTjA1LPj.js → sql-B34HU2DI.js} +74 -17
- package/dist/ssr/index.d.ts +4 -4
- package/dist/ssr/index.js +2 -2
- package/dist/{standard-FUi3Z3-7.js → standard-BRyBGTtp.js} +2 -2
- package/dist/{standard-CyhRzctb.d.ts → standard-BsaCYQJc.d.ts} +1 -1
- package/dist/{stats-C5W-rBfR.js → stats-C_YxA2r1.js} +40 -14
- package/dist/storage/index.d.ts +4 -3
- package/dist/storage/index.js +548 -1
- package/dist/{tables-rZxRxNPW.js → tables-Cb-3_VWc.js} +19 -5
- package/dist/temporal-BwshQk0W.js +27 -0
- package/dist/temporal-required-CBNHT6ni.js +19 -0
- package/dist/testing/index.d.ts +25 -24
- package/dist/testing/index.js +165 -66
- package/dist/{types-C83v5SQz.d.ts → types-DONv9SHN.d.ts} +14 -11
- package/dist/version-Bmf7_FzM.js +4 -0
- package/dist/{view-D1s0wa0T.d.ts → view-B64o81wp.d.ts} +36 -6
- package/dist/{view-aOYizTeE.js → view-BrJI2LKp.js} +11 -2
- package/dist/webhooks/index.d.ts +19 -17
- package/dist/webhooks/index.js +1 -1
- package/dist/{webhooks-DMipjhSP.js → webhooks-Bh_Jf_-l.js} +45 -24
- package/package.json +50 -22
- package/schemas/config-v1.json +31 -1
- package/schemas/doctor-report-v1.json +17 -1
- package/schemas/snapshot-v2.json +35 -0
- package/skills/better-supabase/SKILL.md +77 -18
- package/skills/better-supabase/references/plugins.md +21 -22
- package/skills/better-supabase/references/troubleshooting.md +38 -18
- package/skills/better-supabase-api/SKILL.md +66 -18
- package/skills/better-supabase-api/references/adapters.md +64 -41
- package/skills/better-supabase-auth/SKILL.md +146 -0
- package/skills/better-supabase-auth/references/permdock.md +55 -0
- package/skills/better-supabase-testing/SKILL.md +68 -11
- package/dist/casing-da0uRTqt.js +0 -14
- package/dist/claims-CoTobNC-.js +0 -25
- package/dist/cli-DrSgDOQC.js +0 -6646
- package/dist/env-BEToab0I.js +0 -204
- package/dist/events-BuzTgn2D.js +0 -135
- package/dist/pool-R2m_DkXi.d.ts +0 -37
- package/dist/seed-CzecODcJ.js +0 -101
- package/dist/session-C3LxnQ0e.d.ts +0 -22
- package/dist/session-CqEwzVup.js +0 -20
- package/dist/storage-DuGn38fr.js +0 -527
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
import { a as list, r as defineCliCommand } from "./command-CK14fP91.js";
|
|
2
|
+
import { r as writeIfChanged, t as display } from "./io-k41_z-jT.js";
|
|
3
|
+
import { t as VERSION } from "./version-5mW_8sY1.js";
|
|
4
|
+
import { n as findConfig } from "./config-D9R0o1LH.js";
|
|
5
|
+
import { n as installCommand, t as detectProject } from "./project-B_GeXyHh.js";
|
|
6
|
+
import { posix, resolve } from "node:path";
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
|
+
//#region src/cli/templates.ts
|
|
9
|
+
const INTEGRATIONS = [
|
|
10
|
+
"next",
|
|
11
|
+
"hono",
|
|
12
|
+
"orpc",
|
|
13
|
+
"edge",
|
|
14
|
+
"mcp",
|
|
15
|
+
"client",
|
|
16
|
+
"react"
|
|
17
|
+
];
|
|
18
|
+
const join$1 = (...parts) => posix.normalize(posix.join(...parts));
|
|
19
|
+
const libDir = (context) => join$1(context.srcDir, "lib");
|
|
20
|
+
/** `lib/supabase/`: the shared definition, `server.ts` and `client.ts`. */
|
|
21
|
+
const supabaseDir = (context) => join$1(libDir(context), "supabase");
|
|
22
|
+
/** `lib/supabase/index.ts`, imported as `lib/supabase` on both sides. */
|
|
23
|
+
const libEntry = (context) => join$1(supabaseDir(context), "index.ts");
|
|
24
|
+
/** Import path of `lib/supabase/index.ts`: the folder, unless the project keeps `.ts` extensions. */
|
|
25
|
+
function importLib(context, from) {
|
|
26
|
+
const path = importFrom(context, from, libEntry(context));
|
|
27
|
+
return context.tsExtensions ? path : path.replace(/^(\.\.?\/.+)\/index$/, "$1");
|
|
28
|
+
}
|
|
29
|
+
/** Relative import from one project file to another, following the project's extension style. */
|
|
30
|
+
function importFrom(context, from, to, keepTs = context.tsExtensions) {
|
|
31
|
+
let path = posix.relative(posix.dirname(from), to);
|
|
32
|
+
if (!path.startsWith(".")) path = `./${path}`;
|
|
33
|
+
return keepTs ? path : path.replace(/\.tsx?$/, "");
|
|
34
|
+
}
|
|
35
|
+
function publicEnv(context) {
|
|
36
|
+
const read = (prefix, access) => access === "vite" ? {
|
|
37
|
+
url: `import.meta.env.${prefix}SUPABASE_URL`,
|
|
38
|
+
key: `import.meta.env.${prefix}SUPABASE_PUBLISHABLE_KEY`
|
|
39
|
+
} : {
|
|
40
|
+
url: `process.env.${prefix}SUPABASE_URL!`,
|
|
41
|
+
key: `process.env.${prefix}SUPABASE_PUBLISHABLE_KEY!`
|
|
42
|
+
};
|
|
43
|
+
if (context.frameworks.includes("next")) return read("NEXT_PUBLIC_", "process");
|
|
44
|
+
if (context.frameworks.includes("expo")) return read("EXPO_PUBLIC_", "process");
|
|
45
|
+
if (context.frameworks.includes("vite")) return read("VITE_", "vite");
|
|
46
|
+
return read("", "process");
|
|
47
|
+
}
|
|
48
|
+
const denoJson = (context) => `${JSON.stringify({ imports: {
|
|
49
|
+
"better-supabase": `npm:better-supabase@^${context.version}`,
|
|
50
|
+
"better-supabase/": `npm:/better-supabase@^${context.version}/`,
|
|
51
|
+
"@supabase/supabase-js": "npm:@supabase/supabase-js@^2",
|
|
52
|
+
zod: "npm:zod@^4"
|
|
53
|
+
} }, null, 2)}\n`;
|
|
54
|
+
const SHARED = "supabase/functions/_shared/supabase.ts";
|
|
55
|
+
function shared(context) {
|
|
56
|
+
return {
|
|
57
|
+
path: SHARED,
|
|
58
|
+
contents: `import { defineSupabase } from 'better-supabase';
|
|
59
|
+
|
|
60
|
+
import { schema } from '${importFrom(context, SHARED, context.generated, true)}';
|
|
61
|
+
|
|
62
|
+
export const betterSupabase = defineSupabase(schema);
|
|
63
|
+
`
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** `supabase/functions/<name>/server.ts`: the function's `bs`. */
|
|
67
|
+
function functionServer(name, contents) {
|
|
68
|
+
return {
|
|
69
|
+
path: `supabase/functions/${name}/server.ts`,
|
|
70
|
+
contents: contents("../_shared/supabase.ts")
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/** Edge Functions import `_shared/supabase.ts`, so they don't need the app definition. */
|
|
74
|
+
const needsLib = (names) => names.length === 0 || names.some((name) => name !== "edge" && name !== "mcp");
|
|
75
|
+
/** The files `init` writes: the config and, unless `lib` is false, the isomorphic definition. */
|
|
76
|
+
function baseFiles(context, casing, lib = true) {
|
|
77
|
+
const config = {
|
|
78
|
+
path: "better-supabase.config.ts",
|
|
79
|
+
contents: `import { defineConfig } from 'better-supabase/config';
|
|
80
|
+
|
|
81
|
+
export default defineConfig({
|
|
82
|
+
casing: '${casing}',
|
|
83
|
+
output: '${context.generated}',
|
|
84
|
+
});
|
|
85
|
+
`
|
|
86
|
+
};
|
|
87
|
+
if (!lib) return [config];
|
|
88
|
+
const path = libEntry(context);
|
|
89
|
+
const generated = importFrom(context, path, context.generated);
|
|
90
|
+
return [config, {
|
|
91
|
+
path,
|
|
92
|
+
contents: `import { defineSupabase } from 'better-supabase';
|
|
93
|
+
|
|
94
|
+
import { schema } from '${generated}';
|
|
95
|
+
|
|
96
|
+
export type { Functions, Models } from '${generated}';
|
|
97
|
+
|
|
98
|
+
export const betterSupabase = defineSupabase(schema);
|
|
99
|
+
`
|
|
100
|
+
}];
|
|
101
|
+
}
|
|
102
|
+
const TEMPLATES = {
|
|
103
|
+
client: {
|
|
104
|
+
description: "Browser client with cookie sessions shared with the server",
|
|
105
|
+
requires: [],
|
|
106
|
+
packages: ["@supabase/ssr"],
|
|
107
|
+
files: (context) => {
|
|
108
|
+
const path = join$1(supabaseDir(context), "client.ts");
|
|
109
|
+
const env = publicEnv(context);
|
|
110
|
+
return [{
|
|
111
|
+
path,
|
|
112
|
+
contents: `import { createClient } from 'better-supabase/client';
|
|
113
|
+
|
|
114
|
+
import { betterSupabase } from '${importLib(context, path)}';
|
|
115
|
+
|
|
116
|
+
export const bs = createClient(betterSupabase, {
|
|
117
|
+
env: {
|
|
118
|
+
url: ${env.url},
|
|
119
|
+
publishableKey: ${env.key},
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
`
|
|
123
|
+
}];
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
next: {
|
|
127
|
+
description: "Next.js proxy, Server Components, route handlers and actions",
|
|
128
|
+
requires: ["client"],
|
|
129
|
+
packages: [
|
|
130
|
+
"@supabase/ssr",
|
|
131
|
+
"@supabase/server",
|
|
132
|
+
"server-only"
|
|
133
|
+
],
|
|
134
|
+
files: (context) => {
|
|
135
|
+
const server = join$1(supabaseDir(context), "server.ts");
|
|
136
|
+
const proxy = join$1(context.srcDir, "proxy.ts");
|
|
137
|
+
return [{
|
|
138
|
+
path: server,
|
|
139
|
+
contents: `import 'server-only';
|
|
140
|
+
|
|
141
|
+
import { createNext } from 'better-supabase/next';
|
|
142
|
+
|
|
143
|
+
import { betterSupabase } from '${importLib(context, server)}';
|
|
144
|
+
|
|
145
|
+
export const bs = createNext(betterSupabase);
|
|
146
|
+
`
|
|
147
|
+
}, {
|
|
148
|
+
path: proxy,
|
|
149
|
+
contents: `import type { NextRequest } from 'next/server';
|
|
150
|
+
|
|
151
|
+
import { bs } from '${importFrom(context, proxy, server)}';
|
|
152
|
+
|
|
153
|
+
export const proxy = (request: NextRequest) => bs.proxy(request);
|
|
154
|
+
|
|
155
|
+
export const config = {
|
|
156
|
+
matcher: ['/((?!_next/static|_next/image|favicon.ico|.*\\\\.(?:svg|png|jpg|webp)$).*)'],
|
|
157
|
+
};
|
|
158
|
+
`
|
|
159
|
+
}];
|
|
160
|
+
},
|
|
161
|
+
next: ["Read the session in Server Components with `const { db } = await bs.context()`."]
|
|
162
|
+
},
|
|
163
|
+
react: {
|
|
164
|
+
description: "React provider and typed hooks with TanStack Query",
|
|
165
|
+
requires: ["client"],
|
|
166
|
+
packages: ["@tanstack/react-query"],
|
|
167
|
+
files: (context) => {
|
|
168
|
+
const client = join$1(supabaseDir(context), "client.ts");
|
|
169
|
+
const providers = context.frameworks.includes("next") ? join$1(context.srcDir, "app", "providers.tsx") : join$1(context.srcDir, "providers.tsx");
|
|
170
|
+
const hooks = join$1(libDir(context), "hooks.ts");
|
|
171
|
+
return [{
|
|
172
|
+
path: providers,
|
|
173
|
+
contents: `${context.frameworks.includes("next") ? "'use client';\n\n" : ""}import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
174
|
+
import { BetterSupabaseProvider } from 'better-supabase/react';
|
|
175
|
+
import { type ReactNode, useState } from 'react';
|
|
176
|
+
|
|
177
|
+
import { bs } from '${importFrom(context, providers, client)}';
|
|
178
|
+
|
|
179
|
+
export function Providers({ children }: { children: ReactNode }) {
|
|
180
|
+
const [queryClient] = useState(() => new QueryClient());
|
|
181
|
+
return (
|
|
182
|
+
<QueryClientProvider client={queryClient}>
|
|
183
|
+
<BetterSupabaseProvider client={bs} queryClient={queryClient}>
|
|
184
|
+
{children}
|
|
185
|
+
</BetterSupabaseProvider>
|
|
186
|
+
</QueryClientProvider>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
`
|
|
190
|
+
}, {
|
|
191
|
+
path: hooks,
|
|
192
|
+
contents: `import { createHooks } from 'better-supabase/react';
|
|
193
|
+
|
|
194
|
+
import type { bs } from '${importFrom(context, hooks, client)}';
|
|
195
|
+
|
|
196
|
+
export const { useDb, useQueries, useAuth, useSupabase } = createHooks<typeof bs>();
|
|
197
|
+
`
|
|
198
|
+
}];
|
|
199
|
+
},
|
|
200
|
+
next: ["Wrap your app in <Providers>."]
|
|
201
|
+
},
|
|
202
|
+
hono: {
|
|
203
|
+
description: "Hono middleware, handlers and REST resources",
|
|
204
|
+
requires: [],
|
|
205
|
+
packages: ["hono", "@supabase/server"],
|
|
206
|
+
files: (context) => {
|
|
207
|
+
const path = join$1(context.srcDir, "server.ts");
|
|
208
|
+
return [{
|
|
209
|
+
path,
|
|
210
|
+
contents: `import { Hono } from 'hono';
|
|
211
|
+
import { type HonoEnv, createHono } from 'better-supabase/hono';
|
|
212
|
+
|
|
213
|
+
import { betterSupabase, type Functions, type Models } from '${importLib(context, path)}';
|
|
214
|
+
|
|
215
|
+
export const bs = createHono(betterSupabase);
|
|
216
|
+
|
|
217
|
+
const app = new Hono<HonoEnv<Models, Functions, unknown>>()
|
|
218
|
+
.onError(bs.onError)
|
|
219
|
+
.use('/api/*', bs.middleware())
|
|
220
|
+
.get('/api/me', (c) => c.json({ kind: c.var.auth.kind }));
|
|
221
|
+
|
|
222
|
+
export default app;
|
|
223
|
+
`
|
|
224
|
+
}];
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
orpc: {
|
|
228
|
+
description: "oRPC middleware with typed context and ORPCError mapping",
|
|
229
|
+
requires: [],
|
|
230
|
+
packages: ["@orpc/server", "@supabase/server"],
|
|
231
|
+
files: (context) => {
|
|
232
|
+
const path = join$1(context.srcDir, "router.ts");
|
|
233
|
+
return [{
|
|
234
|
+
path,
|
|
235
|
+
contents: `import { os } from '@orpc/server';
|
|
236
|
+
import { createOrpc, type OrpcRequestContext } from 'better-supabase/orpc';
|
|
237
|
+
|
|
238
|
+
import { betterSupabase } from '${importLib(context, path)}';
|
|
239
|
+
|
|
240
|
+
export const bs = createOrpc(betterSupabase);
|
|
241
|
+
|
|
242
|
+
const base = os.$context<OrpcRequestContext>();
|
|
243
|
+
const authed = base.use(bs.middleware());
|
|
244
|
+
|
|
245
|
+
export const router = {
|
|
246
|
+
me: authed.handler(({ context }) => ({ kind: context.auth.kind })),
|
|
247
|
+
};
|
|
248
|
+
`
|
|
249
|
+
}];
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
edge: {
|
|
253
|
+
description: "A Supabase Edge Function with CORS and Problem Details",
|
|
254
|
+
requires: [],
|
|
255
|
+
packages: [],
|
|
256
|
+
files: (context) => [
|
|
257
|
+
shared(context),
|
|
258
|
+
functionServer("api", (shared) => `import { createEdge } from 'better-supabase/edge';
|
|
259
|
+
|
|
260
|
+
import { betterSupabase } from '${shared}';
|
|
261
|
+
|
|
262
|
+
export const bs = createEdge(betterSupabase, { cors: true });
|
|
263
|
+
`),
|
|
264
|
+
{
|
|
265
|
+
path: "supabase/functions/api/index.ts",
|
|
266
|
+
contents: `import { bs } from './server.ts';
|
|
267
|
+
|
|
268
|
+
Deno.serve(bs.handler((_request, { auth }) => ({ kind: auth.kind })));
|
|
269
|
+
`
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
path: "supabase/functions/api/deno.json",
|
|
273
|
+
contents: denoJson(context)
|
|
274
|
+
}
|
|
275
|
+
],
|
|
276
|
+
next: ["Serve it locally with `supabase functions serve api`."]
|
|
277
|
+
},
|
|
278
|
+
mcp: {
|
|
279
|
+
description: "An MCP server (Streamable HTTP) as an Edge Function",
|
|
280
|
+
requires: [],
|
|
281
|
+
packages: [],
|
|
282
|
+
files: (context) => [
|
|
283
|
+
shared(context),
|
|
284
|
+
functionServer("mcp", (shared) => `import { createMcp } from 'better-supabase/mcp';
|
|
285
|
+
|
|
286
|
+
import { betterSupabase } from '${shared}';
|
|
287
|
+
|
|
288
|
+
export const bs = createMcp(betterSupabase, {
|
|
289
|
+
name: 'app',
|
|
290
|
+
version: '0.1.0',
|
|
291
|
+
resources: {},
|
|
292
|
+
});
|
|
293
|
+
`),
|
|
294
|
+
{
|
|
295
|
+
path: "supabase/functions/mcp/index.ts",
|
|
296
|
+
contents: `import { bs } from './server.ts';
|
|
297
|
+
|
|
298
|
+
Deno.serve(bs.fetch);
|
|
299
|
+
`
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
path: "supabase/functions/mcp/deno.json",
|
|
303
|
+
contents: denoJson(context)
|
|
304
|
+
}
|
|
305
|
+
],
|
|
306
|
+
next: ["List tables under `resources` to expose them as tools, e.g. `customers: true`."]
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
/** The integration and what it requires, dependencies first, without duplicates. */
|
|
310
|
+
function resolveIntegrations(names) {
|
|
311
|
+
const ordered = [];
|
|
312
|
+
const visit = (name) => {
|
|
313
|
+
if (ordered.includes(name)) return;
|
|
314
|
+
for (const dependency of TEMPLATES[name].requires) visit(dependency);
|
|
315
|
+
ordered.push(name);
|
|
316
|
+
};
|
|
317
|
+
for (const name of names) visit(name);
|
|
318
|
+
return ordered;
|
|
319
|
+
}
|
|
320
|
+
function isIntegration(name) {
|
|
321
|
+
return INTEGRATIONS.includes(name);
|
|
322
|
+
}
|
|
323
|
+
/** Integrations `init` adds for the frameworks it finds. */
|
|
324
|
+
function suggestedIntegrations(frameworks) {
|
|
325
|
+
const names = [];
|
|
326
|
+
for (const framework of frameworks) switch (framework) {
|
|
327
|
+
case "next":
|
|
328
|
+
names.push("next");
|
|
329
|
+
break;
|
|
330
|
+
case "hono":
|
|
331
|
+
names.push("hono");
|
|
332
|
+
break;
|
|
333
|
+
case "orpc":
|
|
334
|
+
names.push("orpc");
|
|
335
|
+
break;
|
|
336
|
+
case "vite":
|
|
337
|
+
case "expo":
|
|
338
|
+
names.push("client");
|
|
339
|
+
break;
|
|
340
|
+
case "tanstack-query":
|
|
341
|
+
names.push("react");
|
|
342
|
+
break;
|
|
343
|
+
default: throw new TypeError(`Unknown framework ${String(framework)}`);
|
|
344
|
+
}
|
|
345
|
+
return resolveIntegrations(names);
|
|
346
|
+
}
|
|
347
|
+
//#endregion
|
|
348
|
+
//#region src/cli/commands/init.ts
|
|
349
|
+
const INIT_ARGS = {
|
|
350
|
+
casing: {
|
|
351
|
+
type: "string",
|
|
352
|
+
description: "Row keys in camelCase (camel, the default) or the database's snake_case (snake)",
|
|
353
|
+
valueHint: "camel|snake"
|
|
354
|
+
},
|
|
355
|
+
with: {
|
|
356
|
+
type: "string",
|
|
357
|
+
description: "Integrations to add next to the detected ones: next, hono, orpc, edge, mcp, client, react",
|
|
358
|
+
valueHint: "integration,..."
|
|
359
|
+
},
|
|
360
|
+
force: {
|
|
361
|
+
type: "boolean",
|
|
362
|
+
description: "Overwrite files that exist"
|
|
363
|
+
},
|
|
364
|
+
"dry-run": {
|
|
365
|
+
type: "boolean",
|
|
366
|
+
description: "Show what would be written"
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
const ADD_ARGS = {
|
|
370
|
+
integration: {
|
|
371
|
+
type: "positional",
|
|
372
|
+
required: false,
|
|
373
|
+
description: "next, hono, orpc, edge, mcp, client or react"
|
|
374
|
+
},
|
|
375
|
+
force: {
|
|
376
|
+
type: "boolean",
|
|
377
|
+
description: "Overwrite files that exist"
|
|
378
|
+
},
|
|
379
|
+
"dry-run": {
|
|
380
|
+
type: "boolean",
|
|
381
|
+
description: "Show what would be written"
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
const CANCELLED = {
|
|
385
|
+
code: 1,
|
|
386
|
+
error: "Cancelled."
|
|
387
|
+
};
|
|
388
|
+
/** `--force`, or the person's answer when files exist; `undefined` when they cancel. */
|
|
389
|
+
async function overwrite(root, files, args, prompts) {
|
|
390
|
+
if (args.force === true) return true;
|
|
391
|
+
const existing = [...new Set(files.map((file) => file.path).filter((path) => existsSync(resolve(root, path))))];
|
|
392
|
+
if (!prompts || existing.length === 0) return false;
|
|
393
|
+
return prompts.confirm(`${existing.map((path) => display(root, path)).join(", ")} ${existing.length === 1 ? "exists" : "exist"}. Overwrite?`);
|
|
394
|
+
}
|
|
395
|
+
async function writeFiles(root, files, args, force) {
|
|
396
|
+
const dryRun = args["dry-run"] === true;
|
|
397
|
+
const lines = [];
|
|
398
|
+
const seen = /* @__PURE__ */ new Set();
|
|
399
|
+
for (const file of files) {
|
|
400
|
+
if (seen.has(file.path)) continue;
|
|
401
|
+
seen.add(file.path);
|
|
402
|
+
const path = resolve(root, file.path);
|
|
403
|
+
const shown = display(root, file.path);
|
|
404
|
+
if (existsSync(path) && !force) {
|
|
405
|
+
lines.push(`Kept ${shown} (exists)`);
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
if (dryRun) {
|
|
409
|
+
lines.push(`Would write ${shown}`);
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
lines.push(`${await writeIfChanged(path, file.contents) ? "Wrote " : "Same "} ${shown}`);
|
|
413
|
+
}
|
|
414
|
+
return lines;
|
|
415
|
+
}
|
|
416
|
+
function context(project, generated) {
|
|
417
|
+
return {
|
|
418
|
+
srcDir: project.srcDir,
|
|
419
|
+
generated,
|
|
420
|
+
tsExtensions: project.tsExtensions,
|
|
421
|
+
frameworks: project.frameworks,
|
|
422
|
+
version: VERSION
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
function integrationFiles(names, templateContext) {
|
|
426
|
+
return names.flatMap((name) => TEMPLATES[name].files(templateContext));
|
|
427
|
+
}
|
|
428
|
+
function packagesFor(project, names) {
|
|
429
|
+
const wanted = [
|
|
430
|
+
"better-supabase",
|
|
431
|
+
"@supabase/supabase-js",
|
|
432
|
+
...names.flatMap((name) => TEMPLATES[name].packages)
|
|
433
|
+
];
|
|
434
|
+
return [...new Set(wanted)].filter((name) => !(name in project.dependencies));
|
|
435
|
+
}
|
|
436
|
+
/** pg as a dev dependency, when the project lacks it. */
|
|
437
|
+
function devPackages(project) {
|
|
438
|
+
return "pg" in project.dependencies ? [] : [["pg"]];
|
|
439
|
+
}
|
|
440
|
+
function chooseIntegrations(prompts, initial) {
|
|
441
|
+
return prompts.multiselect("Integrations", INTEGRATIONS.map((name) => ({
|
|
442
|
+
value: name,
|
|
443
|
+
label: name,
|
|
444
|
+
hint: TEMPLATES[name].description
|
|
445
|
+
})), initial);
|
|
446
|
+
}
|
|
447
|
+
function parseIntegrations(values) {
|
|
448
|
+
const unknown = values.filter((value) => !isIntegration(value));
|
|
449
|
+
if (unknown.length > 0) return `Unknown integration ${unknown.join(", ")}. Available: ${INTEGRATIONS.join(", ")}`;
|
|
450
|
+
return values.filter(isIntegration);
|
|
451
|
+
}
|
|
452
|
+
async function runInit(config, args, prompts) {
|
|
453
|
+
const casing = args.casing ?? (prompts ? await prompts.select("Row keys", [{
|
|
454
|
+
value: "camel",
|
|
455
|
+
label: "camelCase",
|
|
456
|
+
hint: "customerId"
|
|
457
|
+
}, {
|
|
458
|
+
value: "snake",
|
|
459
|
+
label: "snake_case",
|
|
460
|
+
hint: "customer_id, as in the database"
|
|
461
|
+
}], "camel") : "camel");
|
|
462
|
+
if (casing === void 0) return CANCELLED;
|
|
463
|
+
if (casing !== "camel" && casing !== "snake") return {
|
|
464
|
+
code: 2,
|
|
465
|
+
error: "--casing must be \"camel\" or \"snake\""
|
|
466
|
+
};
|
|
467
|
+
const project = await detectProject(config.root);
|
|
468
|
+
const requested = parseIntegrations(list(args.with));
|
|
469
|
+
if (typeof requested === "string") return {
|
|
470
|
+
code: 2,
|
|
471
|
+
error: requested
|
|
472
|
+
};
|
|
473
|
+
const detected = resolveIntegrations([...suggestedIntegrations(project.frameworks), ...requested]);
|
|
474
|
+
const chosen = args.with === void 0 && prompts ? await chooseIntegrations(prompts, detected) : detected;
|
|
475
|
+
if (chosen === void 0) return CANCELLED;
|
|
476
|
+
const integrations = resolveIntegrations(chosen);
|
|
477
|
+
const templateContext = context(project, findConfig(config.root) !== void 0 ? config.output : posix.normalize(posix.join(project.srcDir, "lib/supabase/generated.ts")));
|
|
478
|
+
const files = [...baseFiles(templateContext, casing, needsLib(integrations)), ...integrationFiles(integrations, templateContext)];
|
|
479
|
+
const force = await overwrite(config.root, files, args, prompts);
|
|
480
|
+
if (force === void 0) return CANCELLED;
|
|
481
|
+
const lines = [
|
|
482
|
+
project.frameworks.length > 0 ? `Found ${project.frameworks.join(", ")}.` : "No framework found.",
|
|
483
|
+
"",
|
|
484
|
+
...await writeFiles(config.root, files, args, force)
|
|
485
|
+
];
|
|
486
|
+
const packages = packagesFor(project, integrations);
|
|
487
|
+
const steps = [
|
|
488
|
+
...packages.length > 0 ? [installCommand(project.packageManager, packages)] : [],
|
|
489
|
+
...devPackages(project).map((names) => installCommand(project.packageManager, names, true)),
|
|
490
|
+
...project.hasSupabase ? [] : ["supabase init"],
|
|
491
|
+
"supabase start",
|
|
492
|
+
"better-supabase env",
|
|
493
|
+
"better-supabase gen"
|
|
494
|
+
];
|
|
495
|
+
lines.push("", "Next:", ...steps.map((step, index) => ` ${index + 1}. ${step}`));
|
|
496
|
+
for (const name of integrations) for (const hint of TEMPLATES[name].next ?? []) lines.push(` - ${hint}`);
|
|
497
|
+
lines.push("", "Agent skills: better-supabase skills install");
|
|
498
|
+
return {
|
|
499
|
+
code: 0,
|
|
500
|
+
output: lines.join("\n")
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
async function runAdd(config, args, prompts) {
|
|
504
|
+
const named = args._.length === 0 && prompts ? await chooseIntegrations(prompts, []) : args._;
|
|
505
|
+
if (named === void 0) return CANCELLED;
|
|
506
|
+
if (named.length === 0) return {
|
|
507
|
+
code: 2,
|
|
508
|
+
error: `Name at least one integration: ${INTEGRATIONS.join(", ")}`
|
|
509
|
+
};
|
|
510
|
+
const requested = parseIntegrations(named);
|
|
511
|
+
if (typeof requested === "string") return {
|
|
512
|
+
code: 2,
|
|
513
|
+
error: requested
|
|
514
|
+
};
|
|
515
|
+
const project = await detectProject(config.root);
|
|
516
|
+
const integrations = resolveIntegrations(requested);
|
|
517
|
+
const templateContext = context(project, config.output);
|
|
518
|
+
const files = integrationFiles(integrations, templateContext);
|
|
519
|
+
const force = await overwrite(config.root, files, args, prompts);
|
|
520
|
+
if (force === void 0) return CANCELLED;
|
|
521
|
+
const lines = await writeFiles(config.root, files, args, force);
|
|
522
|
+
const lib = libEntry(templateContext);
|
|
523
|
+
if (!existsSync(resolve(config.root, lib)) && integrations.some((name) => needsLib([name]))) lines.push("", `Warning: ${lib} is missing. Run \`better-supabase init\` first.`);
|
|
524
|
+
const packages = packagesFor(project, integrations).filter((name) => name !== "better-supabase" && name !== "@supabase/supabase-js");
|
|
525
|
+
if (packages.length > 0) lines.push("", `Install: ${installCommand(project.packageManager, packages)}`);
|
|
526
|
+
for (const name of integrations) for (const hint of TEMPLATES[name].next ?? []) lines.push(`- ${hint}`);
|
|
527
|
+
return {
|
|
528
|
+
code: 0,
|
|
529
|
+
output: lines.join("\n")
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
const initCommand = defineCliCommand({
|
|
533
|
+
meta: {
|
|
534
|
+
name: "init",
|
|
535
|
+
description: "Writes better-supabase.config.ts, src/lib/supabase/index.ts and glue for the frameworks in package.json"
|
|
536
|
+
},
|
|
537
|
+
args: INIT_ARGS,
|
|
538
|
+
lists: ["with"],
|
|
539
|
+
run: (args, { config, io }) => runInit(config, args, io.prompts)
|
|
540
|
+
});
|
|
541
|
+
const addCommand = defineCliCommand({
|
|
542
|
+
meta: {
|
|
543
|
+
name: "add",
|
|
544
|
+
description: "Adds glue for an integration to an existing project"
|
|
545
|
+
},
|
|
546
|
+
args: ADD_ARGS,
|
|
547
|
+
run: (args, { config, io }) => runAdd(config, args, io.prompts)
|
|
548
|
+
});
|
|
549
|
+
//#endregion
|
|
550
|
+
export { addCommand, initCommand };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { r as defineCliCommand } from "./command-CK14fP91.js";
|
|
2
|
+
import { r as writeIfChanged, t as display } from "./io-k41_z-jT.js";
|
|
3
|
+
import { r as painter } from "./style-C0NFdJRL.js";
|
|
4
|
+
import { l as fileDiff } from "./read-sets-CMa29t9a.js";
|
|
5
|
+
import { _ as restrictSchemas, a as sourceArgs, o as loadSnapshot, t as SOURCE_ARGS, u as serializeSnapshot, v as serializeGenerator } from "./gen-Ccn_p90L.js";
|
|
6
|
+
import { n as withSpinner } from "./prompts-BNcNfAve.js";
|
|
7
|
+
import { resolve } from "node:path";
|
|
8
|
+
import { readFile } from "node:fs/promises";
|
|
9
|
+
//#region src/cli/commands/introspect.ts
|
|
10
|
+
const ARGS = {
|
|
11
|
+
out: {
|
|
12
|
+
type: "string",
|
|
13
|
+
description: "Defaults to supabase/snapshot.json, or supabase/generator-metadata.json",
|
|
14
|
+
valueHint: "file"
|
|
15
|
+
},
|
|
16
|
+
format: {
|
|
17
|
+
type: "string",
|
|
18
|
+
description: "snapshot (the default) or generator-metadata (postgrest-typegen's input)",
|
|
19
|
+
valueHint: "snapshot|generator-metadata"
|
|
20
|
+
},
|
|
21
|
+
check: {
|
|
22
|
+
type: "boolean",
|
|
23
|
+
description: "Fail when the file is out of date"
|
|
24
|
+
},
|
|
25
|
+
...SOURCE_ARGS
|
|
26
|
+
};
|
|
27
|
+
const introspectCommand = defineCliCommand({
|
|
28
|
+
meta: {
|
|
29
|
+
name: "introspect",
|
|
30
|
+
description: "Saves the database schema as a snapshot for offline gen"
|
|
31
|
+
},
|
|
32
|
+
args: ARGS,
|
|
33
|
+
run: async (args, { config, env, io, signal }) => {
|
|
34
|
+
const format = args.format ?? "snapshot";
|
|
35
|
+
if (format !== "snapshot" && format !== "generator-metadata") return {
|
|
36
|
+
code: 2,
|
|
37
|
+
error: "--format must be \"snapshot\" or \"generator-metadata\""
|
|
38
|
+
};
|
|
39
|
+
const { snapshotPath: _ignored, ...source } = await sourceArgs(args, io);
|
|
40
|
+
const snapshot = await withSpinner(io.prompts, "Reading the schema", () => loadSnapshot(config, env, {
|
|
41
|
+
...source,
|
|
42
|
+
live: true,
|
|
43
|
+
...signal ? { signal } : {}
|
|
44
|
+
}));
|
|
45
|
+
const out = args.out ?? (format === "snapshot" ? "supabase/snapshot.json" : "supabase/generator-metadata.json");
|
|
46
|
+
const contents = format === "snapshot" ? serializeSnapshot(snapshot) : `${await serializeGenerator(restrictSchemas(snapshot.generator, config.schemas))}\n`;
|
|
47
|
+
if (args.check === true) {
|
|
48
|
+
const current = await readFile(resolve(config.root, out), "utf8").catch(() => void 0);
|
|
49
|
+
const file = display(config.root, out);
|
|
50
|
+
return current === contents ? {
|
|
51
|
+
code: 0,
|
|
52
|
+
output: `${file} is up to date.`,
|
|
53
|
+
data: {
|
|
54
|
+
file,
|
|
55
|
+
upToDate: true
|
|
56
|
+
}
|
|
57
|
+
} : {
|
|
58
|
+
code: 1,
|
|
59
|
+
output: fileDiff(file, current, contents, painter(io.color)),
|
|
60
|
+
error: `${file} is out of date. Run \`better-supabase introspect\`.`,
|
|
61
|
+
data: {
|
|
62
|
+
file,
|
|
63
|
+
upToDate: false
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const wrote = await writeIfChanged(resolve(config.root, out), contents);
|
|
68
|
+
const tables = snapshot.extras.tables.length;
|
|
69
|
+
return {
|
|
70
|
+
code: 0,
|
|
71
|
+
output: `${wrote ? "Wrote" : "Unchanged"} ${display(config.root, out)} (${tables} tables).`,
|
|
72
|
+
data: {
|
|
73
|
+
file: display(config.root, out),
|
|
74
|
+
written: wrote,
|
|
75
|
+
tables
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
//#endregion
|
|
81
|
+
export { introspectCommand };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { dirname, relative, resolve, sep } from "node:path";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
//#region src/cli/io.ts
|
|
5
|
+
/** Writes a file when its contents changed. Returns whether it wrote. */
|
|
6
|
+
async function writeIfChanged(path, contents) {
|
|
7
|
+
if (existsSync(path)) {
|
|
8
|
+
if (await readFile(path, "utf8") === contents) return false;
|
|
9
|
+
}
|
|
10
|
+
await mkdir(dirname(path), { recursive: true });
|
|
11
|
+
await writeFile(path, contents);
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
/** Relative ESM import specifier from one file to another, keeping `.ts`. */
|
|
15
|
+
function importPath(fromFile, toFile) {
|
|
16
|
+
let path = relative(dirname(fromFile), toFile).split(sep).join("/");
|
|
17
|
+
if (!path.startsWith(".")) path = `./${path}`;
|
|
18
|
+
return path;
|
|
19
|
+
}
|
|
20
|
+
function display(root, path) {
|
|
21
|
+
return relative(root, resolve(root, path)).split(sep).join("/") || ".";
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { importPath as n, writeIfChanged as r, display as t };
|