Skip to content

Commit 8f3c23b

Browse files
manuel3108mstibbardbenmccannAdrianGonz97
authored
feat: supabase (#45)
* add baas category * init attempt * helper script option * admin option. refactor setting env vars * update next steps * demo option * Apply suggestions from benmccann and manuel3108 Co-authored-by: Ben McCann <[email protected]> Co-authored-by: Manuel <[email protected]> * actioning a missed suggestion from benmccann Co-authored-by: Ben McCann <[email protected]> * remove baas category * add packageManager to nextSteps data object * caps reverted somehow * add script execution step between packages and files steps * update config.toml and email template. improve demo * adder.scripts optional * more experimenting * revert script execution experiment * better script experiment * multiselect auth option: basic, magic link, oauth. better nextSteps * fix next steps indenting * use execute commands instead of installing deps * made conditionals names consistent * add password reset for basic auth. fix supabase cli vscode setting gen * add local google oauth. move oauth server-side * add changeset * fix unused imports * move supabase adder * use `@svelte-cli/core` and formatting * fix most important merge conflicts * update adder * re-add scripts in correct place * make supabase adder work * remove external integration type and uses scripts instead * fix linting * avoid "Need to install the following packages:" message if using npx * implement first review feedback * fix categories * fix errors * fix indendation * use svelte 5 syntax * fix lucia adder * remove oauth option * temp * Improve `app.d.ts` robustnes * make `+layout.ts` more robust * Make `+layout.server.ts` more robust * improve robustnes for common files * make `+hooks.server.ts` more robust * fix duplicated handle import * add missing type * fix merge issues * fix arg parsing * fix merge issue * word * simplify script execution * dedent * more tweaks * simplify * no changesets yet * simplify * update pm detector * simplify app namespace * provide highlighter for consistent `nextSteps` * cache packageManager * tweaks * simplify further * a few nits * tab tweaks * extract demos * extract helpers * simplify * Make children in layout non optional * remove ternaries * upgrade package-manager-detector to sync version * remove supabase adder * fix lint? * update detect pm in cli as well --------- Co-authored-by: Matthew Stibbard <[email protected]> Co-authored-by: Ben McCann <[email protected]> Co-authored-by: AdrianGonz97 <[email protected]>
1 parent fda158e commit 8f3c23b

File tree

28 files changed

+466
-406
lines changed

28 files changed

+466
-406
lines changed

community-adder-template/src/config/adder.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ export const adder = defineAdderConfig({
1010
environments: { kit: true, svelte: true }
1111
},
1212
options,
13-
integrationType: 'inline',
1413
packages: [],
1514
files: [
1615
{

packages/adders/common.ts

Lines changed: 300 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1-
import { imports, exports, common } from '@svelte-cli/core/js';
2-
import type { ScriptFileEditor } from '@svelte-cli/core';
1+
import { imports, exports, common, variables, functions } from '@svelte-cli/core/js';
2+
import { Walker, type AstKinds, type AstTypes, type ScriptFileEditor } from '@svelte-cli/core';
33
import type { Question } from '@svelte-cli/core/internal';
44

5+
export function createPrinter(...conditions: boolean[]) {
6+
const printers = conditions.map((condition) => {
7+
return (content: string, alt = '') => (condition ? content : alt);
8+
});
9+
return printers;
10+
}
11+
512
export function addEslintConfigPrettier({ ast }: ScriptFileEditor<Record<string, Question>>) {
613
// if a default import for `eslint-plugin-svelte` already exists, then we'll use their specifier's name instead
714
const importNodes = ast.body.filter((n) => n.type === 'ImportDeclaration');
@@ -59,3 +66,294 @@ export function addEslintConfigPrettier({ ast }: ScriptFileEditor<Record<string,
5966
elements.push(...nodesToInsert);
6067
}
6168
}
69+
70+
export function addGlobalAppInterface(
71+
ast: AstTypes.Program,
72+
name: 'Error' | 'Locals' | 'PageData' | 'PageState' | 'Platform'
73+
) {
74+
let globalDecl = ast.body
75+
.filter((n) => n.type === 'TSModuleDeclaration')
76+
.find((m) => m.global && m.declare);
77+
78+
if (!globalDecl) {
79+
globalDecl = common.statementFromString('declare global {}') as AstTypes.TSModuleDeclaration;
80+
ast.body.push(globalDecl);
81+
}
82+
83+
if (globalDecl.body?.type !== 'TSModuleBlock') {
84+
throw new Error('Unexpected body type of `declare global` in `src/app.d.ts`');
85+
}
86+
87+
let app: AstTypes.TSModuleDeclaration | undefined;
88+
let interfaceNode: AstTypes.TSInterfaceDeclaration | undefined;
89+
90+
// prettier-ignore
91+
Walker.walk(globalDecl as AstTypes.ASTNode, {}, {
92+
TSModuleDeclaration(node, { next }) {
93+
if (node.id.type === 'Identifier' && node.id.name === 'App') {
94+
app = node;
95+
}
96+
next();
97+
},
98+
TSInterfaceDeclaration(node) {
99+
if (node.id.type === 'Identifier' && node.id.name === name) {
100+
interfaceNode = node;
101+
}
102+
},
103+
});
104+
105+
if (!app) {
106+
app = common.statementFromString('namespace App {}') as AstTypes.TSModuleDeclaration;
107+
globalDecl.body.body.push(app);
108+
}
109+
110+
if (app.body?.type !== 'TSModuleBlock') {
111+
throw new Error('Unexpected body type of `namespace App` in `src/app.d.ts`');
112+
}
113+
114+
if (!interfaceNode) {
115+
// add the interface if it's missing
116+
interfaceNode = common.statementFromString(
117+
`interface ${name} {}`
118+
) as AstTypes.TSInterfaceDeclaration;
119+
app.body.body.push(interfaceNode);
120+
}
121+
122+
return interfaceNode;
123+
}
124+
125+
export function hasTypeProp(
126+
name: string,
127+
node: AstTypes.TSInterfaceDeclaration['body']['body'][number]
128+
) {
129+
return (
130+
node.type === 'TSPropertySignature' && node.key.type === 'Identifier' && node.key.name === name
131+
);
132+
}
133+
134+
export function addHooksHandle(
135+
ast: AstTypes.Program,
136+
typescript: boolean,
137+
newHandleName: string,
138+
handleContent: string,
139+
forceSeparateHandle: boolean = false
140+
) {
141+
if (typescript) {
142+
imports.addNamed(ast, '@sveltejs/kit', { Handle: 'Handle' }, true);
143+
}
144+
145+
let isSpecifier: boolean = false;
146+
let handleName = 'handle';
147+
let exportDecl: AstTypes.ExportNamedDeclaration | undefined;
148+
let originalHandleDecl: AstKinds.DeclarationKind | undefined;
149+
150+
// We'll first visit all of the named exports and grab their references if they export `handle`.
151+
// This will grab export references for:
152+
// `export { handle }` & `export { foo as handle }`
153+
// `export const handle = ...`, & `export function handle() {...}`
154+
// prettier-ignore
155+
Walker.walk(ast as AstTypes.ASTNode, {}, {
156+
ExportNamedDeclaration(node) {
157+
let maybeHandleDecl: AstKinds.DeclarationKind | undefined;
158+
159+
// `export { handle }` & `export { foo as handle }`
160+
const handleSpecifier = node.specifiers?.find((s) => s.exported.name === 'handle');
161+
if (handleSpecifier) {
162+
isSpecifier = true;
163+
// we'll search for the local name in case it's aliased (e.g. `export { foo as handle }`)
164+
handleName = handleSpecifier.local?.name ?? handleSpecifier.exported.name;
165+
166+
// find the definition
167+
const handleFunc = ast.body.find((n) => isFunctionDeclaration(n, handleName));
168+
const handleVar = ast.body.find((n) => isVariableDeclaration(n, handleName));
169+
170+
maybeHandleDecl = handleFunc ?? handleVar;
171+
}
172+
173+
maybeHandleDecl ??= node.declaration ?? undefined;
174+
175+
// `export const handle`
176+
if (maybeHandleDecl && isVariableDeclaration(maybeHandleDecl, handleName)) {
177+
exportDecl = node;
178+
originalHandleDecl = maybeHandleDecl;
179+
}
180+
181+
// `export function handle`
182+
if (maybeHandleDecl && isFunctionDeclaration(maybeHandleDecl, handleName)) {
183+
exportDecl = node;
184+
originalHandleDecl = maybeHandleDecl;
185+
}
186+
},
187+
});
188+
189+
const newHandle = common.expressionFromString(handleContent);
190+
if (common.hasNode(ast, newHandle)) return;
191+
192+
// This is the straightforward case. If there's no existing `handle`, we'll just add one
193+
// with the new handle's definition and exit
194+
if (!originalHandleDecl || !exportDecl) {
195+
// handle declaration doesn't exist, so we'll just create it with the hook
196+
const newDecl = variables.declaration(ast, 'const', handleName, newHandle);
197+
if (typescript) {
198+
const declarator = newDecl.declarations[0] as AstTypes.VariableDeclarator;
199+
variables.typeAnnotateDeclarator(declarator, 'Handle');
200+
}
201+
202+
if (!forceSeparateHandle) exports.namedExport(ast, handleName, newDecl);
203+
else {
204+
const newDecl = variables.declaration(ast, 'const', newHandleName, newHandle);
205+
if (typescript) {
206+
const declarator = newDecl.declarations[0] as AstTypes.VariableDeclarator;
207+
variables.typeAnnotateDeclarator(declarator, 'Handle');
208+
}
209+
ast.body.push(newDecl);
210+
211+
const handleDecl = variables.declaration(
212+
ast,
213+
'const',
214+
handleName,
215+
common.expressionFromString(newHandleName)
216+
);
217+
if (typescript) {
218+
const declarator = handleDecl.declarations[0] as AstTypes.VariableDeclarator;
219+
variables.typeAnnotateDeclarator(declarator, 'Handle');
220+
}
221+
exports.namedExport(ast, handleName, handleDecl);
222+
}
223+
224+
return;
225+
}
226+
227+
// create the new handle
228+
const newDecl = variables.declaration(ast, 'const', newHandleName, newHandle);
229+
if (typescript) {
230+
const declarator = newDecl.declarations[0] as AstTypes.VariableDeclarator;
231+
variables.typeAnnotateDeclarator(declarator, 'Handle');
232+
}
233+
234+
// check if `handle` is using a sequence
235+
let sequence: AstTypes.CallExpression | undefined;
236+
if (originalHandleDecl.type === 'VariableDeclaration') {
237+
const handle = originalHandleDecl.declarations.find(
238+
(d) => d.type === 'VariableDeclarator' && usingSequence(d, handleName)
239+
) as AstTypes.VariableDeclarator | undefined;
240+
241+
sequence = handle?.init as AstTypes.CallExpression;
242+
}
243+
244+
// If `handle` is already using a `sequence`, then we'll just create the new handle and
245+
// append the new handle name to the args of `sequence`
246+
// e.g. `export const handle = sequence(some, other, handles, newHandle);`
247+
if (sequence) {
248+
const hasNewArg = sequence.arguments.some(
249+
(arg) => arg.type === 'Identifier' && arg.name === newHandleName
250+
);
251+
if (!hasNewArg) {
252+
sequence.arguments.push(variables.identifier(newHandleName));
253+
}
254+
255+
// removes the declarations so we can append them in the correct order
256+
ast.body = ast.body.filter(
257+
(n) => n !== originalHandleDecl && n !== exportDecl && n !== newDecl
258+
);
259+
if (isSpecifier) {
260+
// if export specifiers are being used (e.g. `export { handle }`), then we'll want
261+
// need to also append original handle declaration as it's not part of the export declaration
262+
ast.body.push(newDecl, originalHandleDecl, exportDecl);
263+
} else {
264+
ast.body.push(newDecl, exportDecl);
265+
}
266+
267+
return;
268+
}
269+
270+
// At this point, the existing `handle` doesn't call `sequence`, so we'll need to rename the original
271+
// `handle` and create a new `handle` that uses `sequence`
272+
// e.g. `const handle = sequence(originalHandle, newHandle);`
273+
const NEW_HANDLE_NAME = 'originalHandle';
274+
const sequenceCall = functions.callByIdentifier('sequence', [NEW_HANDLE_NAME, newHandleName]);
275+
const newHandleDecl = variables.declaration(ast, 'const', handleName, sequenceCall);
276+
277+
imports.addNamed(ast, '@sveltejs/kit/hooks', { sequence: 'sequence' });
278+
279+
let renameRequired = false;
280+
// rename `export const handle`
281+
if (originalHandleDecl && isVariableDeclaration(originalHandleDecl, handleName)) {
282+
const handle = getVariableDeclarator(originalHandleDecl, handleName);
283+
if (handle && handle.id.type === 'Identifier' && handle.init?.type !== 'Identifier') {
284+
renameRequired = true;
285+
handle.id.name = NEW_HANDLE_NAME;
286+
}
287+
}
288+
// rename `export function handle`
289+
if (originalHandleDecl && isFunctionDeclaration(originalHandleDecl, handleName)) {
290+
renameRequired = true;
291+
originalHandleDecl.id!.name = NEW_HANDLE_NAME;
292+
}
293+
294+
// removes all declarations so that we can re-append them in the correct order
295+
ast.body = ast.body.filter((n) => n !== originalHandleDecl && n !== exportDecl && n !== newDecl);
296+
297+
if (isSpecifier) {
298+
ast.body.push(originalHandleDecl, newDecl, newHandleDecl, exportDecl);
299+
}
300+
301+
if (exportDecl.declaration && renameRequired) {
302+
// when we re-append the declarations, we only want to add the declaration
303+
// of the (now renamed) original `handle` _without_ the `export` keyword:
304+
// e.g. `const originalHandle = ...;`
305+
ast.body.push(exportDecl.declaration, newDecl);
306+
// `export const handle = sequence(originalHandle, newHandle);`
307+
exports.namedExport(ast, handleName, newHandleDecl);
308+
} else if (exportDecl.declaration && isVariableDeclaration(originalHandleDecl, handleName)) {
309+
// if the previous value of `export const handle = ...` was an identifier
310+
// there is no need to rename the handle, we just need to add it to the sequence
311+
const variableDeclarator = getVariableDeclarator(originalHandleDecl, handleName);
312+
const sequenceCall = functions.callByIdentifier('sequence', [
313+
(variableDeclarator?.init as AstTypes.Identifier).name,
314+
newHandleName
315+
]);
316+
const newHandleDecl = variables.declaration(ast, 'const', handleName, sequenceCall);
317+
if (typescript) {
318+
const declarator = newHandleDecl.declarations[0] as AstTypes.VariableDeclarator;
319+
variables.typeAnnotateDeclarator(declarator, 'Handle');
320+
}
321+
ast.body.push(newDecl);
322+
exports.namedExport(ast, handleName, newHandleDecl);
323+
}
324+
}
325+
326+
function usingSequence(node: AstTypes.VariableDeclarator, handleName: string) {
327+
return (
328+
node.id.type === 'Identifier' &&
329+
node.id.name === handleName &&
330+
node.init?.type === 'CallExpression' &&
331+
node.init.callee.type === 'Identifier' &&
332+
node.init.callee.name === 'sequence'
333+
);
334+
}
335+
336+
function isVariableDeclaration(
337+
node: AstTypes.ASTNode,
338+
variableName: string
339+
): node is AstTypes.VariableDeclaration {
340+
return (
341+
node.type === 'VariableDeclaration' && getVariableDeclarator(node, variableName) !== undefined
342+
);
343+
}
344+
345+
function getVariableDeclarator(
346+
node: AstTypes.VariableDeclaration,
347+
handleName: string
348+
): AstTypes.VariableDeclarator | undefined {
349+
return node.declarations.find(
350+
(d) => d.type === 'VariableDeclarator' && d.id.type === 'Identifier' && d.id.name === handleName
351+
) as AstTypes.VariableDeclarator | undefined;
352+
}
353+
354+
function isFunctionDeclaration(
355+
node: AstTypes.ASTNode,
356+
funcName: string
357+
): node is AstTypes.FunctionDeclaration {
358+
return node.type === 'FunctionDeclaration' && node.id?.name === funcName;
359+
}

packages/adders/drizzle/config/adder.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ export const adder = defineAdderConfig({
2121
}
2222
},
2323
options: availableOptions,
24-
integrationType: 'inline',
2524
packages: [
2625
{ name: 'drizzle-orm', version: '^0.33.0', dev: false },
2726
{ name: 'drizzle-kit', version: '^0.22.0', dev: true },
@@ -324,15 +323,14 @@ export const adder = defineAdderConfig({
324323
}
325324
}
326325
],
327-
nextSteps: ({ options, colors }) => {
328-
const highlight = (str: string) => colors.bold(colors.cyan(str));
326+
nextSteps: ({ options, highlighter }) => {
329327
const steps = [
330-
`You will need to set ${colors.yellow('DATABASE_URL')} in your production environment`
328+
`You will need to set ${highlighter.env('DATABASE_URL')} in your production environment`
331329
];
332330
if (options.docker) {
333-
steps.push(`Run ${highlight('npm run db:start')} to start the docker container`);
331+
steps.push(`Run ${highlighter.command('npm run db:start')} to start the docker container`);
334332
}
335-
steps.push(`To update your DB schema, run ${highlight('npm run db:push')}`);
333+
steps.push(`To update your DB schema, run ${highlighter.command('npm run db:push')}`);
336334

337335
return steps;
338336
}

packages/adders/eslint/config/adder.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ export const adder = defineAdderConfig({
1818
}
1919
},
2020
options,
21-
integrationType: 'inline',
2221
packages: [
2322
{ name: 'eslint', version: '^9.7.0', dev: true },
2423
{ name: '@types/eslint', version: '^9.6.0', dev: true },

0 commit comments

Comments
 (0)