From 6ece58cd80b5526834e8302d2049996033a45748 Mon Sep 17 00:00:00 2001 From: Matthew Stibbard Date: Mon, 2 Sep 2024 17:10:12 +1000 Subject: [PATCH 01/76] add baas category --- packages/adders/_config/adders/official.ts.rej | 9 +++++++++ packages/adders/_config/categories.ts | 4 ++++ packages/adders/_config/categories.ts.rej | 11 +++++++++++ 3 files changed, 24 insertions(+) create mode 100644 packages/adders/_config/adders/official.ts.rej create mode 100644 packages/adders/_config/categories.ts.rej diff --git a/packages/adders/_config/adders/official.ts.rej b/packages/adders/_config/adders/official.ts.rej new file mode 100644 index 00000000..e788d05c --- /dev/null +++ b/packages/adders/_config/adders/official.ts.rej @@ -0,0 +1,9 @@ +diff a/packages/config/adders/official.ts b/packages/config/adders/official.ts (rejected hunks) +@@ -6,6 +6,7 @@ export const adderCategories: AdderCategories = { + css: ['tailwindcss'], + db: ['drizzle'], + additional: ['storybook', 'mdsvex', 'routify'], ++ baas: ['supabase'], + }; + + export const adderIds = Object.values(adderCategories).flatMap((x) => x); diff --git a/packages/adders/_config/categories.ts b/packages/adders/_config/categories.ts index 7e2bb8a7..beacfc98 100644 --- a/packages/adders/_config/categories.ts +++ b/packages/adders/_config/categories.ts @@ -6,6 +6,10 @@ export type CategoryInfo = { description: string; }; +<<<<<<< HEAD:packages/adders/_config/categories.ts +======= +export type CategoryKeys = 'codeQuality' | 'css' | 'db' | 'testing' | 'additional' | 'baas'; +>>>>>>> a19c799 (add baas category):packages/config/categories.ts export type CategoryDetails = Record; export type AdderCategories = Record; diff --git a/packages/adders/_config/categories.ts.rej b/packages/adders/_config/categories.ts.rej new file mode 100644 index 00000000..fa04a526 --- /dev/null +++ b/packages/adders/_config/categories.ts.rej @@ -0,0 +1,11 @@ +diff a/packages/config/categories.ts b/packages/config/categories.ts (rejected hunks) +@@ -35,4 +35,9 @@ export const categories: CategoryDetails = { + name: 'Database', + description: '', + }, ++ baas: { ++ id: 'baas', ++ name: 'Backend as a Service', ++ description: 'Services that provide database, Auth, storage, etc.', ++ }, + }; From 1090ebdba4726e418884f348995f8f9b013adf51 Mon Sep 17 00:00:00 2001 From: Matthew Stibbard Date: Mon, 2 Sep 2024 17:10:32 +1000 Subject: [PATCH 02/76] init attempt --- adders/supabase/config/adder.ts | 362 ++++++++++++++++++++++++++++++ adders/supabase/config/checks.ts | 6 + adders/supabase/config/options.ts | 9 + adders/supabase/config/tests.ts | 9 + adders/supabase/index.ts | 8 + adders/supabase/supabase.svg | 15 ++ 6 files changed, 409 insertions(+) create mode 100644 adders/supabase/config/adder.ts create mode 100644 adders/supabase/config/checks.ts create mode 100644 adders/supabase/config/options.ts create mode 100644 adders/supabase/config/tests.ts create mode 100644 adders/supabase/index.ts create mode 100644 adders/supabase/supabase.svg diff --git a/adders/supabase/config/adder.ts b/adders/supabase/config/adder.ts new file mode 100644 index 00000000..c23cdfa8 --- /dev/null +++ b/adders/supabase/config/adder.ts @@ -0,0 +1,362 @@ +import { defineAdderConfig, dedent, type TextFileEditorArgs } from '@svelte-add/core'; +import { options as availableOptions } from './options'; + +export const adder = defineAdderConfig({ + metadata: { + id: 'supabase', + name: 'Supabase', + description: `Supabase is an open source Firebase alternative. +Start your project with a Postgres database, Authentication, instant APIs, Edge Functions, Realtime subscriptions, Storage, and Vector embeddings.`, + environments: { svelte: false, kit: true }, + website: { + logo: './supabase.svg', + keywords: ['supabase', 'database', 'postgres', 'auth'], + documentation: 'https://supabase.com/docs', + }, + }, + options: availableOptions, + integrationType: 'inline', + packages: [ + { name: '@supabase/supabase-js', version: '^2.45.3', dev: false }, + { name: '@supabase/ssr', version: '^0.5.1', dev: false }, + // Local development CLI + { + name: 'supabase', + version: '^1.191.3', + dev: true, + condition: ({ options }) => options.cli, + }, + ], + files: [ + { + name: () => `.env`, + contentType: 'text', + content: generateEnvFileContent, + }, + { + name: () => `.env.example`, + contentType: 'text', + content: generateEnvFileContent, + }, + { + name: ({ typescript }) => `./src/hooks.server.${typescript.installed ? 'ts' : 'js'}`, + contentType: 'text', + content: ({ typescript }) => { + return dedent` + import { createServerClient } from '@supabase/ssr' + import {${typescript.installed ? ' type Handle,' : ''} redirect } from '@sveltejs/kit' + import { sequence } from '@sveltejs/kit/hooks' + + import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public' + + const supabase${typescript.installed ? ': Handle' : ''} = async ({ event, resolve }) => { + event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { + cookies: { + getAll: () => event.cookies.getAll(), + setAll: (cookiesToSet) => { + cookiesToSet.forEach(({ name, value, options }) => { + event.cookies.set(name, value, { ...options, path: '/' }) + }) + }, + }, + }) + + event.locals.safeGetSession = async () => { + const { + data: { session }, + } = await event.locals.supabase.auth.getSession() + if (!session) { + return { session: null, user: null } + } + + const { + data: { user }, + error, + } = await event.locals.supabase.auth.getUser() + if (error) { + return { session: null, user: null } + } + + return { session, user } + } + + return resolve(event, { + filterSerializedResponseHeaders(name) { + return name === 'content-range' || name === 'x-supabase-api-version' + }, + }) + } + + const authGuard${typescript.installed ? ': Handle' : ''} = async ({ event, resolve }) => { + const { session, user } = await event.locals.safeGetSession() + event.locals.session = session + event.locals.user = user + + if (!event.locals.session && event.url.pathname.startsWith('/private')) { + redirect(303, '/auth') + } + + if (event.locals.session && event.url.pathname === '/auth') { + redirect(303, '/private') + } + + return resolve(event) + } + + export const handle${typescript.installed ? ': Handle' : ''} = sequence(supabase, authGuard) + `; + }, + }, + { + name: () => './src/app.d.ts', + contentType: 'text', + condition: ({ typescript }) => typescript.installed, + content: () => { + return dedent` + import type { Session, SupabaseClient, User } from '@supabase/supabase-js' + + declare global { + namespace App { + // interface Error {} + interface Locals { + supabase: SupabaseClient + safeGetSession: () => Promise<{ session: Session | null; user: User | null }> + session: Session | null + user: User | null + } + interface PageData { + session: Session | null + } + // interface PageState {} + // interface Platform {} + } + } + + export {} + `; + }, + }, + { + name: ({ kit, typescript }) => + `${kit.routesDirectory}/+layout.${typescript.installed ? 'ts' : 'js'}`, + contentType: 'text', + content: ({ typescript }) => { + return dedent` + import { createBrowserClient, createServerClient, isBrowser } from '@supabase/ssr' + import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public' + ${typescript.installed ? `import type { LayoutLoad } from './$types'\n` : ''} + export const load${typescript.installed ? ': LayoutLoad' : ''} = async ({ data, depends, fetch }) => { + depends('supabase:auth') + + const supabase = isBrowser() + ? createBrowserClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { + global: { fetch }, + }) + : createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { + global: { fetch }, + cookies: { + getAll() { + return data.cookies + }, + }, + }) + + const { + data: { session }, + } = await supabase.auth.getSession() + + const { + data: { user }, + } = await supabase.auth.getUser() + + return { session, supabase, user } + } + `; + }, + }, + { + name: ({ kit, typescript }) => + `${kit.routesDirectory}/+layout.server.${typescript.installed ? 'ts' : 'js'}`, + contentType: 'text', + content: ({ typescript }) => { + return dedent` + ${typescript.installed ? `import type { LayoutServerLoad } from './$types'\n` : ''} + export const load${typescript.installed ? ': LayoutServerLoad' : ''} = async ({ locals: { session }, cookies }) => { + return { + session, + cookies: cookies.getAll(), + } + } + `; + }, + }, + { + name: ({ kit }) => `${kit.routesDirectory}/+layout.svelte`, + contentType: 'text', + content: ({ typescript }) => { + return dedent` + + import { invalidate } from '$app/navigation'; + import { onMount } from 'svelte'; + + export let data; + $: ({ session, supabase } = data); + + onMount(() => { + const { data } = supabase.auth.onAuthStateChange((_, newSession) => { + if (newSession?.expires_at !== session?.expires_at) { + invalidate('supabase:auth'); + } + }); + + return () => data.subscription.unsubscribe(); + }); + + + + `; + }, + }, + { + name: ({ kit, typescript }) => + `${kit.routesDirectory}/auth/+page.server.${typescript.installed ? 'ts' : 'js'}`, + contentType: 'text', + content: ({ typescript }) => { + return dedent` + import { redirect } from '@sveltejs/kit' + ${typescript.installed ? `import type { Actions } from './$types'` : ''} + export const actions${typescript.installed ? `: Actions` : ''} = { + signup: async ({ request, locals: { supabase } }) => { + const formData = await request.formData() + const email = formData.get('email')${typescript.installed ? ' as string' : ''} + const password = formData.get('password')${typescript.installed ? ' as string' : ''} + + const { error } = await supabase.auth.signUp({ email, password }) + if (error) { + console.error(error) + redirect(303, '/auth/error') + } else { + redirect(303, '/') + } + }, + login: async ({ request, locals: { supabase } }) => { + const formData = await request.formData() + const email = formData.get('email')${typescript.installed ? ' as string' : ''} + const password = formData.get('password')${typescript.installed ? ' as string' : ''} + + const { error } = await supabase.auth.signInWithPassword({ email, password }) + if (error) { + console.error(error) + redirect(303, '/auth/error') + } else { + redirect(303, '/private') + } + }, + } + `; + }, + }, + { + name: ({ kit }) => `${kit.routesDirectory}/auth/+page.svelte`, + contentType: 'text', + content: () => { + return dedent` +
+ + + + +
+ `; + }, + }, + { + name: ({ kit }) => `${kit.routesDirectory}/auth/error/+page.svelte`, + contentType: 'text', + content: () => { + return '

Login error

'; + }, + }, + { + name: ({ kit, typescript }) => + `${kit.routesDirectory}/auth/confirm/+server.${typescript.installed ? 'ts' : 'js'}`, + contentType: 'text', + content: ({ typescript }) => { + return dedent` + import { redirect } from '@sveltejs/kit' + ${ + typescript.installed + ? dedent`import type { EmailOtpType } from '@supabase/supabase-js' + import type { RequestHandler } from './$types' + ` + : '' + } + export const GET${typescript.installed ? ': RequestHandler' : ''} = async ({ url, locals: { supabase } }) => { + const token_hash = url.searchParams.get('token_hash') + const type = url.searchParams.get('type')${typescript.installed ? ' as EmailOtpType | null' : ''} + const next = url.searchParams.get('next') ?? '/' + + const redirectTo = new URL(url) + redirectTo.pathname = next + redirectTo.searchParams.delete('token_hash') + redirectTo.searchParams.delete('type') + + if (token_hash && type) { + const { error } = await supabase.auth.verifyOtp({ type, token_hash }) + if (!error) { + redirectTo.searchParams.delete('next') + redirect(303, redirectTo) + } + } + + redirectTo.pathname = '/auth/error' + redirect(303, redirectTo) + } + `; + }, + }, + ], + nextSteps: ({ options }) => { + const steps = ['Visit the Supabase docs: https://supabase.com/docs']; + + if (options.cli) { + steps.push('Read the Supabase CLI Docs: https://supabase.com/docs/reference/cli'); + } + + return steps; + }, +}); + +function generateEnvFileContent({ content, options }: TextFileEditorArgs) { + if (options.cli) { + // Local development CLI always has the same credentials + content = addEnvVar(content, 'PUBLIC_SUPABASE_URL', '"http://127.0.0.1:54321"'); + content = addEnvVar( + content, + 'PUBLIC_SUPABASE_ANON_KEY', + '"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0"', + ); + } else { + content = addEnvVar(content, 'PUBLIC_SUPABASE_URL', '""'); + content = addEnvVar(content, 'PUBLIC_SUPABASE_ANON_KEY', '""'); + } + return content; +} + +function addEnvVar(content: string, key: string, value: string) { + if (!content.includes(key + '=')) { + content = appendEnvContent(content, `${key}=${value}`); + } + return content; +} + +function appendEnvContent(existing: string, content: string) { + const withNewLine = !existing.length || existing.endsWith('\n') ? existing : existing + '\n'; + return withNewLine + content + '\n'; +} diff --git a/adders/supabase/config/checks.ts b/adders/supabase/config/checks.ts new file mode 100644 index 00000000..fc08d6f3 --- /dev/null +++ b/adders/supabase/config/checks.ts @@ -0,0 +1,6 @@ +import { defineAdderChecks } from '@svelte-add/core'; +import { options } from './options'; + +export const checks = defineAdderChecks({ + options, +}); diff --git a/adders/supabase/config/options.ts b/adders/supabase/config/options.ts new file mode 100644 index 00000000..ac86ef14 --- /dev/null +++ b/adders/supabase/config/options.ts @@ -0,0 +1,9 @@ +import { defineAdderOptions } from '@svelte-add/core'; + +export const options = defineAdderOptions({ + cli: { + question: 'Do you want to install the Supabase CLI for local development?', + type: 'boolean', + default: true, + }, +}); diff --git a/adders/supabase/config/tests.ts b/adders/supabase/config/tests.ts new file mode 100644 index 00000000..524e74cc --- /dev/null +++ b/adders/supabase/config/tests.ts @@ -0,0 +1,9 @@ +import { defineAdderTests } from '@svelte-add/core'; +import { options } from './options.js'; + +export const tests = defineAdderTests({ + files: [], + options, + optionValues: [], + tests: [], +}); diff --git a/adders/supabase/index.ts b/adders/supabase/index.ts new file mode 100644 index 00000000..08f7ba75 --- /dev/null +++ b/adders/supabase/index.ts @@ -0,0 +1,8 @@ +#!/usr/bin/env node + +import { defineAdder } from '@svelte-add/core'; +import { adder } from './config/adder.js'; +import { tests } from './config/tests.js'; +import { checks } from './config/checks.js'; + +export default defineAdder(adder, checks, tests); diff --git a/adders/supabase/supabase.svg b/adders/supabase/supabase.svg new file mode 100644 index 00000000..ad802ac1 --- /dev/null +++ b/adders/supabase/supabase.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + From a70bc62ff600e99ec16679e27bd668519a32895e Mon Sep 17 00:00:00 2001 From: Matthew Stibbard Date: Mon, 2 Sep 2024 17:31:33 +1000 Subject: [PATCH 03/76] helper script option --- adders/supabase/config/adder.ts | 22 +++++++++++++++++++--- adders/supabase/config/options.ts | 8 +++++++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/adders/supabase/config/adder.ts b/adders/supabase/config/adder.ts index c23cdfa8..672a192e 100644 --- a/adders/supabase/config/adder.ts +++ b/adders/supabase/config/adder.ts @@ -111,15 +111,15 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge name: () => './src/app.d.ts', contentType: 'text', condition: ({ typescript }) => typescript.installed, - content: () => { + content: ({ options }) => { return dedent` import type { Session, SupabaseClient, User } from '@supabase/supabase-js' - + ${options.cli && options.helpers ? `import type { Database } from '$lib/supabase-types'\n` : ''} declare global { namespace App { // interface Error {} interface Locals { - supabase: SupabaseClient + supabase: SupabaseClient${options.cli && options.helpers ? `` : ''} safeGetSession: () => Promise<{ session: Session | null; user: User | null }> session: Session | null user: User | null @@ -321,6 +321,22 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge `; }, }, + { + name: () => `package.json`, + contentType: 'json', + content: ({ data, typescript }) => { + data.scripts ??= {}; + const scripts: Record = data.scripts; + scripts['db:migration'] ??= 'supabase migration new'; + scripts['db:migration:up'] ??= 'supabase migration up --local'; + scripts['db:reset'] ??= 'supabase db reset'; + if (typescript.installed) { + scripts['db:types'] ??= + 'supabase gen types typescript --local > src/lib/supabase-types.ts'; + } + }, + condition: ({ options }) => options.helpers, + }, ], nextSteps: ({ options }) => { const steps = ['Visit the Supabase docs: https://supabase.com/docs']; diff --git a/adders/supabase/config/options.ts b/adders/supabase/config/options.ts index ac86ef14..7c499b11 100644 --- a/adders/supabase/config/options.ts +++ b/adders/supabase/config/options.ts @@ -1,4 +1,4 @@ -import { defineAdderOptions } from '@svelte-add/core'; +import { colors, defineAdderOptions } from '@svelte-add/core'; export const options = defineAdderOptions({ cli: { @@ -6,4 +6,10 @@ export const options = defineAdderOptions({ type: 'boolean', default: true, }, + helpers: { + question: `Do you want to add Supabase helper scripts to your package.json? E.g., ${colors.yellow('db:reset')} and ${colors.yellow('db:migration "description"')}`, + type: 'boolean', + default: false, + condition: ({ cli }) => cli === true, + }, }); From 17f5e0b2d02ebb81c0c9b67cf10ce17e0f124505 Mon Sep 17 00:00:00 2001 From: Matthew Stibbard Date: Mon, 2 Sep 2024 18:11:48 +1000 Subject: [PATCH 04/76] admin option. refactor setting env vars --- adders/supabase/config/adder.ts | 63 +++++++++++++++++++++++++------ adders/supabase/config/options.ts | 5 +++ 2 files changed, 56 insertions(+), 12 deletions(-) diff --git a/adders/supabase/config/adder.ts b/adders/supabase/config/adder.ts index 672a192e..460b035a 100644 --- a/adders/supabase/config/adder.ts +++ b/adders/supabase/config/adder.ts @@ -321,6 +321,31 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge `; }, }, + { + name: ({ kit, typescript }) => + `${kit.libDirectory}/server/supabase-admin.${typescript.installed ? 'ts' : 'js'}`, + contentType: 'text', + content: ({ options, typescript }) => { + return dedent` + import { PUBLIC_SUPABASE_URL } from '$env/static/public' + import { SUPABASE_SERVICE_ROLE_KEY } from '$env/static/private' + ${typescript && options.cli && options.helpers ? `import type { Database } from '$lib/supabase-types'\n` : ''} + import { createClient } from '@supabase/supabase-js' + + export const supabaseAdmin = createClient${typescript && options.cli && options.helpers ? '' : ''}( + PUBLIC_SUPABASE_URL, + SUPABASE_SERVICE_ROLE_KEY, + { + auth: { + autoRefreshToken: false, + persistSession: false, + }, + }, + ); + `; + }, + condition: ({ options }) => options.admin, + }, { name: () => `package.json`, contentType: 'json', @@ -350,18 +375,32 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge }); function generateEnvFileContent({ content, options }: TextFileEditorArgs) { - if (options.cli) { - // Local development CLI always has the same credentials - content = addEnvVar(content, 'PUBLIC_SUPABASE_URL', '"http://127.0.0.1:54321"'); - content = addEnvVar( - content, - 'PUBLIC_SUPABASE_ANON_KEY', - '"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0"', - ); - } else { - content = addEnvVar(content, 'PUBLIC_SUPABASE_URL', '""'); - content = addEnvVar(content, 'PUBLIC_SUPABASE_ANON_KEY', '""'); - } + content = addEnvVar( + content, + 'PUBLIC_SUPABASE_URL', + // Local development env always has the same credentials, prepopulate the local dev env file + options.cli ? '"http://127.0.0.1:54321"' : '""', + ); + content = addEnvVar( + content, + 'PUBLIC_SUPABASE_ANON_KEY', + // Local development env always has the same credentials, prepopulate the local dev env file + options.cli + ? '"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0"' + : '""', + ); + + content = options.admin + ? addEnvVar( + content, + 'SUPABASE_SERVICE_ROLE_KEY', + // Local development env always has the same credentials, prepopulate the local dev env file + options.cli + ? '"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU"' + : '""', + ) + : content; + return content; } diff --git a/adders/supabase/config/options.ts b/adders/supabase/config/options.ts index 7c499b11..03921429 100644 --- a/adders/supabase/config/options.ts +++ b/adders/supabase/config/options.ts @@ -1,6 +1,11 @@ import { colors, defineAdderOptions } from '@svelte-add/core'; export const options = defineAdderOptions({ + admin: { + question: `Do you want to add an Supabase admin client? ${colors.red('Warning: This client bypasses row level security, only use server side.')}`, + type: 'boolean', + default: false, + }, cli: { question: 'Do you want to install the Supabase CLI for local development?', type: 'boolean', From 7398d399fddacdf2d6659db1ab4462a9c29c8db3 Mon Sep 17 00:00:00 2001 From: Matthew Stibbard Date: Mon, 2 Sep 2024 21:10:50 +1000 Subject: [PATCH 05/76] update next steps --- adders/supabase/config/adder.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/adders/supabase/config/adder.ts b/adders/supabase/config/adder.ts index 460b035a..a74ccf21 100644 --- a/adders/supabase/config/adder.ts +++ b/adders/supabase/config/adder.ts @@ -1,4 +1,4 @@ -import { defineAdderConfig, dedent, type TextFileEditorArgs } from '@svelte-add/core'; +import { defineAdderConfig, dedent, type TextFileEditorArgs, colors } from '@svelte-add/core'; import { options as availableOptions } from './options'; export const adder = defineAdderConfig({ @@ -364,10 +364,20 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge }, ], nextSteps: ({ options }) => { - const steps = ['Visit the Supabase docs: https://supabase.com/docs']; + const steps = [ + 'Visit the Supabase docs: https://supabase.com/docs', + 'Update the authGuard server hook function with your protected routes', + ]; if (options.cli) { - steps.push('Read the Supabase CLI Docs: https://supabase.com/docs/reference/cli'); + steps.push( + dedent`Local development environment: + + 1. Initialize the local development environment: ${colors.yellow('pnpm supabase init')} + 2. Start the local development services: ${colors.yellow('pnpm supabase start')}. This may take a while the first time you run it + 3. Depending on your Auth selections, you may need to configure local email templates and modify ${colors.green('./supabase/config.toml')} + `, + ); } return steps; From f9e6353cfdc2aeb9cf02183788093b3c65a561ed Mon Sep 17 00:00:00 2001 From: Matthew Stibbard Date: Mon, 2 Sep 2024 22:42:39 +1000 Subject: [PATCH 06/76] demo option --- adders/supabase/config/adder.ts | 166 +++++++++++++++++++++++++++++- adders/supabase/config/options.ts | 5 + 2 files changed, 167 insertions(+), 4 deletions(-) diff --git a/adders/supabase/config/adder.ts b/adders/supabase/config/adder.ts index a74ccf21..0358df59 100644 --- a/adders/supabase/config/adder.ts +++ b/adders/supabase/config/adder.ts @@ -41,7 +41,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge { name: ({ typescript }) => `./src/hooks.server.${typescript.installed ? 'ts' : 'js'}`, contentType: 'text', - content: ({ typescript }) => { + content: ({ options, typescript }) => { return dedent` import { createServerClient } from '@supabase/ssr' import {${typescript.installed ? ' type Handle,' : ''} redirect } from '@sveltejs/kit' @@ -91,7 +91,9 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge const { session, user } = await event.locals.safeGetSession() event.locals.session = session event.locals.user = user - + ${ + options.demo + ? ` if (!event.locals.session && event.url.pathname.startsWith('/private')) { redirect(303, '/auth') } @@ -99,7 +101,11 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge if (event.locals.session && event.url.pathname === '/auth') { redirect(303, '/private') } - + ` + : ` + // Add authentication guards here + ` + } return resolve(event) } @@ -362,6 +368,157 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge }, condition: ({ options }) => options.helpers, }, + // Demo routes + { + name: ({ kit, typescript }) => + `${kit.routesDirectory}/private/+layout.server.${typescript.installed ? 'ts' : 'js'}`, + contentType: 'text', + content: () => { + return dedent` + /** + * This file is necessary to ensure protection of all routes in the \`private\` + * directory. It makes the routes in this directory _dynamic_ routes, which + * send a server request, and thus trigger \`hooks.server.ts\`. + **/ + `; + }, + condition: ({ options }) => options.demo, + }, + { + name: ({ kit }) => `${kit.routesDirectory}/private/+layout.svelte`, + contentType: 'text', + content: () => { + return dedent` + + +
+ + +
+
+ +
+ `; + }, + condition: ({ options }) => options.demo, + }, + { + name: ({ kit }) => `${kit.routesDirectory}/private/+page.svelte`, + contentType: 'text', + content: ({ options, typescript }) => { + return dedent` + + import { invalidate } from '$app/navigation' + ${typescript.installed ? `import type { EventHandler } from 'svelte/elements'\n` : ''} + ${typescript.installed ? `import type { PageData } from './$types'\n` : ''} + export let data${typescript.installed ? ': PageData' : ''} + + $: ({ ${options.cli ? 'notes, supabase, user' : 'user'} } = data) + ${ + options.cli + ? ` + let handleSubmit${typescript.installed ? ': EventHandler' : ''} + $: handleSubmit = async (evt) => { + evt.preventDefault(); + if (!evt.target) return; + + const form = evt.target${typescript.installed ? ' as HTMLFormElement' : ''} + + const note = (new FormData(form).get('note') ?? '')${typescript.installed ? ' as string' : ''} + if (!note) return; + + const { error } = await supabase.from('notes').insert({ note }); + if (error) console.error(error); + + invalidate('supabase:db:notes'); + form.reset(); + } + ` + : '' + } + + +

Private page for user: {user?.email}

+ ${ + options.cli + ? ` +

Notes

+
    + {#each notes as note} +
  • {note.note}
  • + {/each} +
+
+ +
+ ` + : '' + } + `; + }, + condition: ({ options }) => options.demo, + }, + { + name: ({ kit, typescript }) => + `${kit.routesDirectory}/private/+page.server.${typescript.installed ? 'ts' : 'js'}`, + contentType: 'text', + content: ({ typescript }) => { + return dedent` + ${typescript.installed ? `import type { PageServerLoad } from './$types'\n` : ''} + export const load${typescript.installed ? ': PageServerLoad' : ''} = async ({ depends, locals: { supabase } }) => { + depends('supabase:db:notes') + const { data: notes } = await supabase.from('notes').select('id,note').order('id') + return { notes: notes ?? [] } + } + `; + }, + condition: ({ options }) => options.demo && options.cli, + }, + { + name: () => './supabase/migrations/00000000000000_demo.sql', + contentType: 'text', + content: () => { + return dedent` + create table notes ( + id bigint primary key generated always as identity, + created_at timestamp with time zone not null default now(), + user_id uuid references auth.users on delete cascade not null default auth.uid(), + note text not null + ); + + alter table notes enable row level security; + + revoke all on table notes from authenticated; + revoke all on table notes from anon; + + grant all (note) on table notes to authenticated; + grant select (id) on table notes to authenticated; + grant delete on table notes to authenticated; + + create policy "Users can access and modify their own notes" + on notes + for all + to authenticated + using ((select auth.uid()) = user_id); + `; + }, + condition: ({ options }) => options.demo && options.cli, + }, ], nextSteps: ({ options }) => { const steps = [ @@ -375,7 +532,8 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge 1. Initialize the local development environment: ${colors.yellow('pnpm supabase init')} 2. Start the local development services: ${colors.yellow('pnpm supabase start')}. This may take a while the first time you run it - 3. Depending on your Auth selections, you may need to configure local email templates and modify ${colors.green('./supabase/config.toml')} + 3. Update ${colors.green('./supabase/config.toml')} [auth] section \`site_url\` and \`additional_redirect_urls\` to use port 5173 + 4. Depending on your Auth selections, you may need to create local email templates and update ${colors.green('./supabase/config.toml')} `, ); } diff --git a/adders/supabase/config/options.ts b/adders/supabase/config/options.ts index 03921429..441d6e9a 100644 --- a/adders/supabase/config/options.ts +++ b/adders/supabase/config/options.ts @@ -1,6 +1,11 @@ import { colors, defineAdderOptions } from '@svelte-add/core'; export const options = defineAdderOptions({ + demo: { + question: 'Do you want to include demo routes to show protected routes?', + type: 'boolean', + default: false, + }, admin: { question: `Do you want to add an Supabase admin client? ${colors.red('Warning: This client bypasses row level security, only use server side.')}`, type: 'boolean', From 03124704aff834e5c5a6340a7ee564d0a3fa7b7c Mon Sep 17 00:00:00 2001 From: Stibbs Date: Tue, 3 Sep 2024 10:27:13 +1000 Subject: [PATCH 07/76] Apply suggestions from benmccann and manuel3108 Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> Co-authored-by: Manuel <30698007+manuel3108@users.noreply.github.com> --- adders/supabase/config/options.ts | 2 +- packages/adders/_config/adders/official.ts.rej | 9 ++++++--- packages/adders/_config/categories.ts | 4 ++++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/adders/supabase/config/options.ts b/adders/supabase/config/options.ts index 441d6e9a..71e4e7df 100644 --- a/adders/supabase/config/options.ts +++ b/adders/supabase/config/options.ts @@ -7,7 +7,7 @@ export const options = defineAdderOptions({ default: false, }, admin: { - question: `Do you want to add an Supabase admin client? ${colors.red('Warning: This client bypasses row level security, only use server side.')}`, + question: `Do you want to add a Supabase admin client? ${colors.red('Warning: This client bypasses row level security, only use server side.')}`, type: 'boolean', default: false, }, diff --git a/packages/adders/_config/adders/official.ts.rej b/packages/adders/_config/adders/official.ts.rej index e788d05c..863f0be4 100644 --- a/packages/adders/_config/adders/official.ts.rej +++ b/packages/adders/_config/adders/official.ts.rej @@ -1,9 +1,12 @@ diff a/packages/config/adders/official.ts b/packages/config/adders/official.ts (rejected hunks) -@@ -6,6 +6,7 @@ export const adderCategories: AdderCategories = { +@@ -4,9 +4,8 @@ export const adderCategories: AdderCategories = { + codeQuality: ['prettier', 'eslint'], + testing: ['vitest', 'playwright'], css: ['tailwindcss'], - db: ['drizzle'], +- db: ['drizzle'], ++ db: ['drizzle', 'supabase'], additional: ['storybook', 'mdsvex', 'routify'], -+ baas: ['supabase'], +- baas: ['supabase'], }; export const adderIds = Object.values(adderCategories).flatMap((x) => x); diff --git a/packages/adders/_config/categories.ts b/packages/adders/_config/categories.ts index beacfc98..7f94ee20 100644 --- a/packages/adders/_config/categories.ts +++ b/packages/adders/_config/categories.ts @@ -6,10 +6,14 @@ export type CategoryInfo = { description: string; }; +<<<<<<< HEAD:packages/adders/_config/categories.ts <<<<<<< HEAD:packages/adders/_config/categories.ts ======= export type CategoryKeys = 'codeQuality' | 'css' | 'db' | 'testing' | 'additional' | 'baas'; >>>>>>> a19c799 (add baas category):packages/config/categories.ts +======= +export type CategoryKeys = 'codeQuality' | 'css' | 'db' | 'testing' | 'additional'; +>>>>>>> f47d2f4 (Apply suggestions from benmccann and manuel3108):packages/config/categories.ts export type CategoryDetails = Record; export type AdderCategories = Record; From 48008a94c5a1b8960d3d5c36837b7059e611448b Mon Sep 17 00:00:00 2001 From: Stibbs Date: Tue, 3 Sep 2024 10:28:20 +1000 Subject: [PATCH 08/76] actioning a missed suggestion from benmccann Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> --- adders/supabase/config/adder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adders/supabase/config/adder.ts b/adders/supabase/config/adder.ts index 0358df59..1305c46b 100644 --- a/adders/supabase/config/adder.ts +++ b/adders/supabase/config/adder.ts @@ -6,7 +6,7 @@ export const adder = defineAdderConfig({ id: 'supabase', name: 'Supabase', description: `Supabase is an open source Firebase alternative. -Start your project with a Postgres database, Authentication, instant APIs, Edge Functions, Realtime subscriptions, Storage, and Vector embeddings.`, +Start your project with a Postgres database, authentication, instant APIs, edge functions, realtime subscriptions, storage, and vector embeddings.`, environments: { svelte: false, kit: true }, website: { logo: './supabase.svg', From dab765f32847baa75471b87a1881d667ea869d39 Mon Sep 17 00:00:00 2001 From: Matthew Stibbard Date: Tue, 3 Sep 2024 11:11:23 +1000 Subject: [PATCH 09/76] remove baas category --- packages/adders/_config/categories.ts.rej | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/adders/_config/categories.ts.rej b/packages/adders/_config/categories.ts.rej index fa04a526..50918f9b 100644 --- a/packages/adders/_config/categories.ts.rej +++ b/packages/adders/_config/categories.ts.rej @@ -1,11 +1,11 @@ diff a/packages/config/categories.ts b/packages/config/categories.ts (rejected hunks) -@@ -35,4 +35,9 @@ export const categories: CategoryDetails = { +@@ -35,9 +35,4 @@ export const categories: CategoryDetails = { name: 'Database', description: '', }, -+ baas: { -+ id: 'baas', -+ name: 'Backend as a Service', -+ description: 'Services that provide database, Auth, storage, etc.', -+ }, +- baas: { +- id: 'baas', +- name: 'Backend as a Service', +- description: 'Services that provide database, Auth, storage, etc.', +- }, }; From 777b3ee0e21db3f2c1c7336589cd123e748e8713 Mon Sep 17 00:00:00 2001 From: Matthew Stibbard Date: Tue, 3 Sep 2024 12:04:54 +1000 Subject: [PATCH 10/76] add packageManager to nextSteps data object --- adders/supabase/config/adder.ts | 6 +-- packages/core/adder/config.ts | 18 +++++++ packages/core/adder/nextSteps.ts.rej | 29 +++++++++++ packages/core/utils/dependencies.ts.rej | 64 +++++++++++++++++++++++++ 4 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 packages/core/adder/nextSteps.ts.rej create mode 100644 packages/core/utils/dependencies.ts.rej diff --git a/adders/supabase/config/adder.ts b/adders/supabase/config/adder.ts index 1305c46b..be9fa7e8 100644 --- a/adders/supabase/config/adder.ts +++ b/adders/supabase/config/adder.ts @@ -520,7 +520,7 @@ Start your project with a Postgres database, authentication, instant APIs, edge condition: ({ options }) => options.demo && options.cli, }, ], - nextSteps: ({ options }) => { + nextSteps: ({ options, packageManager }) => { const steps = [ 'Visit the Supabase docs: https://supabase.com/docs', 'Update the authGuard server hook function with your protected routes', @@ -530,8 +530,8 @@ Start your project with a Postgres database, authentication, instant APIs, edge steps.push( dedent`Local development environment: - 1. Initialize the local development environment: ${colors.yellow('pnpm supabase init')} - 2. Start the local development services: ${colors.yellow('pnpm supabase start')}. This may take a while the first time you run it + 1. Initialize the local development environment: ${colors.yellow(`${packageManager} supabase init`)} + 2. Start the local development services: ${colors.yellow(`${packageManager} supabase start`)}. This may take a while the first time you run it 3. Update ${colors.green('./supabase/config.toml')} [auth] section \`site_url\` and \`additional_redirect_urls\` to use port 5173 4. Depending on your Auth selections, you may need to create local email templates and update ${colors.green('./supabase/config.toml')} `, diff --git a/packages/core/adder/config.ts b/packages/core/adder/config.ts index 4e42224e..63ad0a95 100644 --- a/packages/core/adder/config.ts +++ b/packages/core/adder/config.ts @@ -1,7 +1,24 @@ +<<<<<<< HEAD import type { OptionDefinition, OptionValues, Question } from './options.ts'; import type { FileType } from '../files/processors.ts'; import type { Workspace } from '../files/workspace.ts'; import type { Colors } from 'picocolors/types.ts'; +======= +import type { + CssAstEditor, + HtmlAstEditor, + JsAstEditor, + SvelteAstEditor +} from '@svelte-cli/ast-manipulation'; +import type { OptionDefinition, OptionValues, Question } from './options.js'; +import type { FileTypes } from '../files/processors.js'; +import type { Workspace } from '../utils/workspace.js'; +import type { Postcondition } from './postconditions.js'; +import type { Colors } from 'picocolors/types.js'; +import type { PackageManager } from '../utils/dependencies.js'; + +export type { CssAstEditor, HtmlAstEditor, JsAstEditor, SvelteAstEditor }; +>>>>>>> e647284 (add packageManager to nextSteps data object) export type ConditionDefinition = ( Workspace: Workspace @@ -51,6 +68,7 @@ export type InlineAdderConfig = BaseAdderConfig string[]; }; diff --git a/packages/core/adder/nextSteps.ts.rej b/packages/core/adder/nextSteps.ts.rej new file mode 100644 index 00000000..b286b148 --- /dev/null +++ b/packages/core/adder/nextSteps.ts.rej @@ -0,0 +1,29 @@ +diff a/packages/core/adder/nextSteps.ts b/packages/core/adder/nextSteps.ts (rejected hunks) +@@ -1,14 +1,18 @@ ++import { detectPackageManager } from '../utils/dependencies'; + import { messagePrompt } from '../utils/prompts'; + import type { InlineAdderConfig } from './config'; + import type { AdderDetails, AddersExecutionPlan } from './execute'; + import type { OptionDefinition, OptionValues } from './options'; + import pc from 'picocolors'; + +-export function displayNextSteps( ++export async function displayNextSteps( + adderDetails: AdderDetails[], + multipleAdders: boolean, + executionPlan: AddersExecutionPlan, + ) { ++ const packageManager = await detectPackageManager(executionPlan.workingDirectory); ++ if (!packageManager) throw new Error('Unable to detect package manager'); ++ + const allAddersMessage = adderDetails + .filter((x) => x.config.integrationType == 'inline' && x.config.nextSteps) + .map((x) => x.config as InlineAdderConfig) +@@ -28,6 +32,7 @@ export function displayNextSteps( + cwd: executionPlan.workingDirectory, + colors: pc, + docs: x.metadata.website?.documentation, ++ packageManager, + }); + adderMessage += `- ${adderNextSteps.join('\n- ')}`; + return adderMessage; diff --git a/packages/core/utils/dependencies.ts.rej b/packages/core/utils/dependencies.ts.rej new file mode 100644 index 00000000..44237446 --- /dev/null +++ b/packages/core/utils/dependencies.ts.rej @@ -0,0 +1,64 @@ +diff a/packages/core/utils/dependencies.ts b/packages/core/utils/dependencies.ts (rejected hunks) +@@ -4,9 +4,21 @@ import { COMMANDS } from 'package-manager-detector/agents'; + import { spinner } from '@svelte-add/clack-prompts'; + import { executeCli } from './cli.js'; + +-type PackageManager = (typeof packageManagers)[number] | undefined; ++export type PackageManager = (typeof packageManagers)[number]; + const packageManagers = ['npm', 'pnpm', 'yarn', 'bun'] as const; + ++/** ++ * @param workingDirectory ++ * @returns the package manager ++ */ ++export async function detectPackageManager( ++ workingDirectory: string, ++): Promise { ++ const detectedPm = await detect({ cwd: workingDirectory }); ++ const pm = normalizePackageManager(detectedPm.agent); ++ return pm; ++} ++ + /** + * @param workingDirectory + * @returns the install status of dependencies +@@ -14,22 +26,23 @@ const packageManagers = ['npm', 'pnpm', 'yarn', 'bun'] as const; + export async function suggestInstallingDependencies( + workingDirectory: string, + ): Promise<'installed' | 'skipped'> { +- const detectedPm = await detect({ cwd: workingDirectory }); +- let selectedPm = detectedPm.agent; +- +- selectedPm ??= await selectPrompt( +- 'Which package manager do you want to install dependencies with?', +- undefined, +- [ +- { +- label: 'None', +- value: undefined, +- }, +- ...packageManagers.map((x) => { +- return { label: x, value: x as PackageManager }; +- }), +- ], +- ); ++ let selectedPm = await detectPackageManager(workingDirectory); ++ ++ if (!selectedPm) { ++ selectedPm = await selectPrompt( ++ 'Which package manager do you want to install dependencies with?', ++ undefined, ++ [ ++ { ++ label: 'None', ++ value: undefined, ++ }, ++ ...packageManagers.map((x) => { ++ return { label: x, value: x }; ++ }), ++ ], ++ ); ++ } + + if (!selectedPm || !COMMANDS[selectedPm]) { + return 'skipped'; From 85153cb073c7801516a99e912742c0a3a4197cbb Mon Sep 17 00:00:00 2001 From: Matthew Stibbard Date: Tue, 3 Sep 2024 15:06:46 +1000 Subject: [PATCH 11/76] caps reverted somehow --- adders/supabase/config/adder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adders/supabase/config/adder.ts b/adders/supabase/config/adder.ts index be9fa7e8..fe494bf7 100644 --- a/adders/supabase/config/adder.ts +++ b/adders/supabase/config/adder.ts @@ -6,7 +6,7 @@ export const adder = defineAdderConfig({ id: 'supabase', name: 'Supabase', description: `Supabase is an open source Firebase alternative. -Start your project with a Postgres database, authentication, instant APIs, edge functions, realtime subscriptions, storage, and vector embeddings.`, +Start your project with a Postgres database, Authentication, instant APIs, Edge Functions, Realtime subscriptions, Storage, and Vector embeddings.`, environments: { svelte: false, kit: true }, website: { logo: './supabase.svg', From ec79552576f8c73774d4308d00d0382fb253f8b1 Mon Sep 17 00:00:00 2001 From: Matthew Stibbard Date: Wed, 4 Sep 2024 11:44:11 +1000 Subject: [PATCH 12/76] add script execution step between packages and files steps --- adders/supabase/config/adder.ts | 7 +++++++ packages/core/adder/config.ts | 6 ++++++ packages/core/adder/config.ts.rej | 9 +++++++++ packages/core/adder/execute.ts.rej | 23 +++++++++++++++++++++++ packages/core/files/processors.ts | 22 ++++++++++++++++++++++ packages/core/files/processors.ts.rej | 13 +++++++++++++ packages/core/utils/workspace.ts.rej | 17 +++++++++++++++++ 7 files changed, 97 insertions(+) create mode 100644 packages/core/adder/config.ts.rej create mode 100644 packages/core/adder/execute.ts.rej create mode 100644 packages/core/files/processors.ts.rej create mode 100644 packages/core/utils/workspace.ts.rej diff --git a/adders/supabase/config/adder.ts b/adders/supabase/config/adder.ts index fe494bf7..194ca08d 100644 --- a/adders/supabase/config/adder.ts +++ b/adders/supabase/config/adder.ts @@ -27,6 +27,13 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge condition: ({ options }) => options.cli, }, ], + scripts: [ + { + description: 'Supabase CLI initialization', + args: ['supabase', 'init', '--with-intellij-settings false', '--with-vscode-settings false'], + condition: ({ options }) => options.cli, + }, + ], files: [ { name: () => `.env`, diff --git a/packages/core/adder/config.ts b/packages/core/adder/config.ts index 63ad0a95..e4e36b27 100644 --- a/packages/core/adder/config.ts +++ b/packages/core/adder/config.ts @@ -52,6 +52,12 @@ export type PackageDefinition = { condition?: ConditionDefinition; }; +export type Scripts = { + description: string; + args: string[]; + condition?: ConditionDefinition; +}; + export type BaseAdderConfig = { metadata: AdderConfigMetadata; options: Args; diff --git a/packages/core/adder/config.ts.rej b/packages/core/adder/config.ts.rej new file mode 100644 index 00000000..39cbd784 --- /dev/null +++ b/packages/core/adder/config.ts.rej @@ -0,0 +1,9 @@ +diff a/packages/core/adder/config.ts b/packages/core/adder/config.ts (rejected hunks) +@@ -55,6 +61,7 @@ export type BaseAdderConfig = { + export type InlineAdderConfig = BaseAdderConfig & { + integrationType: 'inline'; + packages: PackageDefinition[]; ++ scripts: Scripts[]; + files: FileTypes[]; + nextSteps?: (data: { + options: OptionValues; diff --git a/packages/core/adder/execute.ts.rej b/packages/core/adder/execute.ts.rej new file mode 100644 index 00000000..7500fba7 --- /dev/null +++ b/packages/core/adder/execute.ts.rej @@ -0,0 +1,23 @@ +diff a/packages/core/adder/execute.ts b/packages/core/adder/execute.ts (rejected hunks) +@@ -3,7 +3,7 @@ import * as pc from 'picocolors'; + import { serializeJson } from '@svelte-add/ast-tooling'; + import { commonFilePaths, format, writeFile } from '../files/utils.js'; + import { type ProjectType, createProject, detectSvelteDirectory } from '../utils/create-project.js'; +-import { createOrUpdateFiles } from '../files/processors.js'; ++import { createOrUpdateFiles, executeScripts } from '../files/processors.js'; + import { getPackageJson } from '../utils/common.js'; + import { + type Workspace, +@@ -281,10 +281,11 @@ async function processInlineAdder( + isInstall: boolean, + ) { + const pkgPath = await installPackages(config, workspace); ++ const scriptsExecuted = await executeScripts(config.scripts, workspace); + const updatedOrCreatedFiles = await createOrUpdateFiles(config.files, workspace); + await runHooks(config, workspace, isInstall); + +- const changedFiles = [pkgPath, ...updatedOrCreatedFiles]; ++ const changedFiles = [pkgPath, ...scriptsExecuted, ...updatedOrCreatedFiles]; + return changedFiles; + } + diff --git a/packages/core/files/processors.ts b/packages/core/files/processors.ts index 7f430cb7..5d53ea31 100644 --- a/packages/core/files/processors.ts +++ b/packages/core/files/processors.ts @@ -71,6 +71,28 @@ type BaseFile = { export type FileType = BaseFile & ParsedFile; +export async function executeScripts( + scripts: Scripts[], + workspace: Workspace, +): Promise { + const scriptsExecuted = []; + + for (const script of scripts) { + if (script.condition && !script.condition(workspace)) { + continue; + } + try { + await executeCli(workspace.packageManager, script.args, workspace.cwd); + } catch (error) { + const typedError = error as Error; + throw new Error('Failed to execute package scripts: ' + typedError.message); + } + scriptsExecuted.push(script.description); + } + + return scriptsExecuted; +} + /** * @param files * @param workspace diff --git a/packages/core/files/processors.ts.rej b/packages/core/files/processors.ts.rej new file mode 100644 index 00000000..86ac752e --- /dev/null +++ b/packages/core/files/processors.ts.rej @@ -0,0 +1,13 @@ +diff a/packages/core/files/processors.ts b/packages/core/files/processors.ts (rejected hunks) +@@ -20,9 +20,10 @@ import { + serializeSvelteFile, + } from '@svelte-add/ast-tooling'; + import { fileExistsWorkspace, readFile, writeFile } from './utils.js'; +-import type { ConditionDefinition } from '../adder/config.js'; ++import type { ConditionDefinition, Scripts } from '../adder/config.js'; + import type { OptionDefinition } from '../adder/options.js'; + import type { Workspace } from '../utils/workspace.js'; ++import { executeCli } from '../utils/cli.js'; + + export type BaseFile = { + name: (options: Workspace) => string; diff --git a/packages/core/utils/workspace.ts.rej b/packages/core/utils/workspace.ts.rej new file mode 100644 index 00000000..dcd0c4ab --- /dev/null +++ b/packages/core/utils/workspace.ts.rej @@ -0,0 +1,17 @@ +diff a/packages/core/utils/workspace.ts b/packages/core/utils/workspace.ts (rejected hunks) +@@ -5,6 +5,7 @@ import { getJsAstEditor } from '@svelte-add/ast-manipulation'; + import type { OptionDefinition, OptionValues, Question } from '../adder/options.js'; + import { remoteControl } from '../internal.js'; + import path from 'path'; ++import { detectPackageManager } from './dependencies.js'; + + export type PrettierData = { + installed: boolean; +@@ -46,6 +48,7 @@ export function createEmptyWorkspace(): Workspace + routesDirectory: 'src/routes', + libDirectory: 'src/lib', + }, ++ packageManager: '', + } as Workspace; + } + From 3473f145c34eb6828506503c5ca209222dc1c344 Mon Sep 17 00:00:00 2001 From: Matthew Stibbard Date: Wed, 4 Sep 2024 14:52:33 +1000 Subject: [PATCH 13/76] update config.toml and email template. improve demo --- adders/supabase/config/adder.ts | 148 ++++++++++++++++++++++++-------- 1 file changed, 114 insertions(+), 34 deletions(-) diff --git a/adders/supabase/config/adder.ts b/adders/supabase/config/adder.ts index 194ca08d..5644ca3b 100644 --- a/adders/supabase/config/adder.ts +++ b/adders/supabase/config/adder.ts @@ -234,22 +234,39 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge name: ({ kit, typescript }) => `${kit.routesDirectory}/auth/+page.server.${typescript.installed ? 'ts' : 'js'}`, contentType: 'text', - content: ({ typescript }) => { + content: ({ options, typescript }) => { return dedent` import { redirect } from '@sveltejs/kit' - ${typescript.installed ? `import type { Actions } from './$types'` : ''} + import { PUBLIC_BASE_URL } from '$env/static/public' + ${typescript.installed ? `import type { Actions } from './$types'\n` : ''} export const actions${typescript.installed ? `: Actions` : ''} = { signup: async ({ request, locals: { supabase } }) => { const formData = await request.formData() const email = formData.get('email')${typescript.installed ? ' as string' : ''} const password = formData.get('password')${typescript.installed ? ' as string' : ''} - const { error } = await supabase.auth.signUp({ email, password }) + const { error } = await supabase.auth.signUp(${ + options.demo + ? ` + { + email, + password, + options: { + emailRedirectTo: \`\${PUBLIC_BASE_URL}/private\`, + } + }` + : '{ email, password }' + }) if (error) { console.error(error) - redirect(303, '/auth/error') + return { message: 'Something went wrong, please try again.' } } else { - redirect(303, '/') + ${ + options.demo + ? `// Redirect to local Inbucket for demo purposes + redirect(303, \`http://localhost:54324/m/\${email}\`)` + : `return { message: 'Sign up succeeded! Please check your email inbox.' }` + } } }, login: async ({ request, locals: { supabase } }) => { @@ -260,7 +277,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge const { error } = await supabase.auth.signInWithPassword({ email, password }) if (error) { console.error(error) - redirect(303, '/auth/error') + return { message: 'Something went wrong, please try again.' } } else { redirect(303, '/private') } @@ -272,9 +289,15 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge { name: ({ kit }) => `${kit.routesDirectory}/auth/+page.svelte`, contentType: 'text', - content: () => { + content: ({ typescript }) => { return dedent` -
+ + +
+
+						User: {JSON.stringify($page.data.user, null, 2)}
+					
+ `; + }, + condition: ({ options }) => options.demo, + }, { name: ({ kit, typescript }) => `${kit.routesDirectory}/private/+layout.server.${typescript.installed ? 'ts' : 'js'}`, @@ -526,6 +564,47 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge }, condition: ({ options }) => options.demo && options.cli, }, + { + name: () => './supabase/config.toml', + contentType: 'text', + content: ({ content }) => { + content = content.replace('"http://127.0.0.1:3000"', '"http://localhost:5173"'); + content = content.replace('"https://127.0.0.1:3000"', '"https://localhost:5173/*"'); + content = content.replace('enable_confirmations = false', 'enable_confirmations = true'); + + content = appendContent( + content, + dedent` + \n# Custom email confirmation template + [auth.email.template.confirmation] + subject = "Confirm Your Signup" + content_path = "./supabase/templates/confirmation.html" + `, + ); + + return content; + }, + condition: ({ options }) => options.cli, + }, + { + name: () => './supabase/templates/confirmation.html', + contentType: 'text', + content: () => { + return dedent` + + +

Confirm your signup

+

Follow this link to confirm your user:

+

Confirm your email

+ + + `; + }, + condition: ({ options }) => options.cli, + }, ], nextSteps: ({ options, packageManager }) => { const steps = [ @@ -550,6 +629,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge }); function generateEnvFileContent({ content, options }: TextFileEditorArgs) { + content = addEnvVar(content, 'PUBLIC_BASE_URL', '"http://localhost:5173"'); content = addEnvVar( content, 'PUBLIC_SUPABASE_URL', @@ -581,12 +661,12 @@ function generateEnvFileContent({ content, options }: TextFileEditorArgs Date: Wed, 4 Sep 2024 17:21:30 +1000 Subject: [PATCH 14/76] adder.scripts optional --- packages/core/adder/config.ts.rej | 5 +++-- packages/core/adder/execute.ts.rej | 22 +++++++++------------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/packages/core/adder/config.ts.rej b/packages/core/adder/config.ts.rej index 39cbd784..1e757056 100644 --- a/packages/core/adder/config.ts.rej +++ b/packages/core/adder/config.ts.rej @@ -1,9 +1,10 @@ diff a/packages/core/adder/config.ts b/packages/core/adder/config.ts (rejected hunks) -@@ -55,6 +61,7 @@ export type BaseAdderConfig = { +@@ -61,7 +61,7 @@ export type BaseAdderConfig = { export type InlineAdderConfig = BaseAdderConfig & { integrationType: 'inline'; packages: PackageDefinition[]; -+ scripts: Scripts[]; +- scripts: Scripts[]; ++ scripts?: Scripts[]; files: FileTypes[]; nextSteps?: (data: { options: OptionValues; diff --git a/packages/core/adder/execute.ts.rej b/packages/core/adder/execute.ts.rej index 7500fba7..f6f8dc54 100644 --- a/packages/core/adder/execute.ts.rej +++ b/packages/core/adder/execute.ts.rej @@ -1,23 +1,19 @@ diff a/packages/core/adder/execute.ts b/packages/core/adder/execute.ts (rejected hunks) -@@ -3,7 +3,7 @@ import * as pc from 'picocolors'; - import { serializeJson } from '@svelte-add/ast-tooling'; - import { commonFilePaths, format, writeFile } from '../files/utils.js'; - import { type ProjectType, createProject, detectSvelteDirectory } from '../utils/create-project.js'; --import { createOrUpdateFiles } from '../files/processors.js'; -+import { createOrUpdateFiles, executeScripts } from '../files/processors.js'; - import { getPackageJson } from '../utils/common.js'; - import { - type Workspace, -@@ -281,10 +281,11 @@ async function processInlineAdder( +@@ -281,11 +281,15 @@ async function processInlineAdder( isInstall: boolean, ) { const pkgPath = await installPackages(config, workspace); -+ const scriptsExecuted = await executeScripts(config.scripts, workspace); +- const scriptsExecuted = await executeScripts(config.scripts, workspace); ++ ++ if (config.scripts && config.scripts.length > 0) { ++ await executeScripts(config.scripts, workspace); ++ } ++ const updatedOrCreatedFiles = await createOrUpdateFiles(config.files, workspace); await runHooks(config, workspace, isInstall); -- const changedFiles = [pkgPath, ...updatedOrCreatedFiles]; -+ const changedFiles = [pkgPath, ...scriptsExecuted, ...updatedOrCreatedFiles]; +- const changedFiles = [pkgPath, ...scriptsExecuted, ...updatedOrCreatedFiles]; ++ const changedFiles = [pkgPath, ...updatedOrCreatedFiles]; return changedFiles; } From 6fd7e0c58d4b17543e3ec0117d6f2333d56df628 Mon Sep 17 00:00:00 2001 From: Matthew Stibbard Date: Wed, 4 Sep 2024 20:41:17 +1000 Subject: [PATCH 15/76] more experimenting --- packages/core/adder/execute.ts.rej | 33 ++++--- packages/core/adder/nextSteps.ts.rej | 19 ++-- packages/core/files/processors.ts | 6 ++ packages/core/files/processors.ts.rej | 11 +-- packages/core/utils/create-project.ts.rej | 8 ++ packages/core/utils/dependencies.ts.rej | 108 +++++++++++----------- packages/core/utils/workspace.ts.rej | 10 +- 7 files changed, 102 insertions(+), 93 deletions(-) create mode 100644 packages/core/utils/create-project.ts.rej diff --git a/packages/core/adder/execute.ts.rej b/packages/core/adder/execute.ts.rej index f6f8dc54..08d375e7 100644 --- a/packages/core/adder/execute.ts.rej +++ b/packages/core/adder/execute.ts.rej @@ -1,19 +1,18 @@ diff a/packages/core/adder/execute.ts b/packages/core/adder/execute.ts (rejected hunks) -@@ -281,11 +281,15 @@ async function processInlineAdder( - isInstall: boolean, - ) { - const pkgPath = await installPackages(config, workspace); -- const scriptsExecuted = await executeScripts(config.scripts, workspace); -+ -+ if (config.scripts && config.scripts.length > 0) { -+ await executeScripts(config.scripts, workspace); -+ } -+ - const updatedOrCreatedFiles = await createOrUpdateFiles(config.files, workspace); - await runHooks(config, workspace, isInstall); - -- const changedFiles = [pkgPath, ...scriptsExecuted, ...updatedOrCreatedFiles]; -+ const changedFiles = [pkgPath, ...updatedOrCreatedFiles]; - return changedFiles; - } +@@ -28,7 +28,7 @@ import type { + InlineAdderConfig, + } from './config.js'; + import type { RemoteControlOptions } from './remoteControl.js'; +-import { suggestInstallingDependencies } from '../utils/dependencies.js'; ++import { suggestInstallingDependencies, type PackageManager } from '../utils/dependencies.js'; + import { validatePreconditions } from './preconditions.js'; + import { endPrompts, startPrompts } from '../utils/prompts.js'; + import { checkPostconditions, printUnmetPostconditions } from './postconditions.js'; +@@ -102,6 +103,7 @@ export async function executeAdders( + commonCliOptions, + cliOptionsByAdderId, + selectAddersToApply, ++ packageManager: undefined, + }; + await executePlan(executionPlan, executingAdder, adderDetails, remoteControlOptions); diff --git a/packages/core/adder/nextSteps.ts.rej b/packages/core/adder/nextSteps.ts.rej index b286b148..9be23483 100644 --- a/packages/core/adder/nextSteps.ts.rej +++ b/packages/core/adder/nextSteps.ts.rej @@ -1,29 +1,30 @@ diff a/packages/core/adder/nextSteps.ts b/packages/core/adder/nextSteps.ts (rejected hunks) -@@ -1,14 +1,18 @@ -+import { detectPackageManager } from '../utils/dependencies'; +@@ -1,18 +1,14 @@ +-import { detectPackageManager } from '../utils/dependencies'; import { messagePrompt } from '../utils/prompts'; import type { InlineAdderConfig } from './config'; import type { AdderDetails, AddersExecutionPlan } from './execute'; import type { OptionDefinition, OptionValues } from './options'; import pc from 'picocolors'; --export function displayNextSteps( -+export async function displayNextSteps( +-export async function displayNextSteps( ++export function displayNextSteps( adderDetails: AdderDetails[], multipleAdders: boolean, executionPlan: AddersExecutionPlan, ) { -+ const packageManager = await detectPackageManager(executionPlan.workingDirectory); -+ if (!packageManager) throw new Error('Unable to detect package manager'); -+ +- const packageManager = await detectPackageManager(executionPlan.workingDirectory); +- if (!packageManager) throw new Error('Unable to detect package manager'); +- const allAddersMessage = adderDetails .filter((x) => x.config.integrationType == 'inline' && x.config.nextSteps) .map((x) => x.config as InlineAdderConfig) -@@ -28,6 +32,7 @@ export function displayNextSteps( +@@ -32,7 +28,7 @@ export async function displayNextSteps( cwd: executionPlan.workingDirectory, colors: pc, docs: x.metadata.website?.documentation, -+ packageManager, +- packageManager, ++ packageManager: executionPlan.packageManager ?? 'pnpm', }); adderMessage += `- ${adderNextSteps.join('\n- ')}`; return adderMessage; diff --git a/packages/core/files/processors.ts b/packages/core/files/processors.ts index 5d53ea31..ca6746cc 100644 --- a/packages/core/files/processors.ts +++ b/packages/core/files/processors.ts @@ -77,6 +77,12 @@ export async function executeScripts( ): Promise { const scriptsExecuted = []; + if (scripts.length === 0) return []; + if (!workspace.packageManager) return []; + + const installCommand = COMMANDS[workspace.packageManager].install; + await installDependencies(workspace.packageManager, [installCommand], workspace.cwd); + for (const script of scripts) { if (script.condition && !script.condition(workspace)) { continue; diff --git a/packages/core/files/processors.ts.rej b/packages/core/files/processors.ts.rej index 86ac752e..04b2b0e8 100644 --- a/packages/core/files/processors.ts.rej +++ b/packages/core/files/processors.ts.rej @@ -1,13 +1,10 @@ diff a/packages/core/files/processors.ts b/packages/core/files/processors.ts (rejected hunks) -@@ -20,9 +20,10 @@ import { - serializeSvelteFile, - } from '@svelte-add/ast-tooling'; - import { fileExistsWorkspace, readFile, writeFile } from './utils.js'; --import type { ConditionDefinition } from '../adder/config.js'; -+import type { ConditionDefinition, Scripts } from '../adder/config.js'; +@@ -24,6 +24,8 @@ import type { ConditionDefinition, Scripts } from '../adder/config.js'; import type { OptionDefinition } from '../adder/options.js'; import type { Workspace } from '../utils/workspace.js'; -+import { executeCli } from '../utils/cli.js'; + import { executeCli } from '../utils/cli.js'; ++import { COMMANDS } from 'package-manager-detector/agents'; ++import { installDependencies } from '../utils/dependencies.js'; export type BaseFile = { name: (options: Workspace) => string; diff --git a/packages/core/utils/create-project.ts.rej b/packages/core/utils/create-project.ts.rej new file mode 100644 index 00000000..7cea212d --- /dev/null +++ b/packages/core/utils/create-project.ts.rej @@ -0,0 +1,8 @@ +diff a/packages/core/utils/create-project.ts b/packages/core/utils/create-project.ts (rejected hunks) +@@ -119,5 +127,6 @@ export async function createProject(cwd: string, supportKit: boolean, supportSve + return { + projectCreated: true, + directory: path.join(process.cwd(), directory), ++ packageManager, + }; + } diff --git a/packages/core/utils/dependencies.ts.rej b/packages/core/utils/dependencies.ts.rej index 44237446..4bc457e2 100644 --- a/packages/core/utils/dependencies.ts.rej +++ b/packages/core/utils/dependencies.ts.rej @@ -1,64 +1,60 @@ diff a/packages/core/utils/dependencies.ts b/packages/core/utils/dependencies.ts (rejected hunks) -@@ -4,9 +4,21 @@ import { COMMANDS } from 'package-manager-detector/agents'; - import { spinner } from '@svelte-add/clack-prompts'; - import { executeCli } from './cli.js'; +@@ -11,25 +11,14 @@ const packageManagers = ['npm', 'pnpm', 'yarn', 'bun'] as const; + * @param workingDirectory + * @returns the package manager + */ +-export async function detectPackageManager( ++export async function selectPackageManager( + workingDirectory: string, + ): Promise { + const detectedPm = await detect({ cwd: workingDirectory }); +- const pm = normalizePackageManager(detectedPm.agent); +- return pm; +-} ++ let pm = normalizePackageManager(detectedPm.agent); --type PackageManager = (typeof packageManagers)[number] | undefined; -+export type PackageManager = (typeof packageManagers)[number]; - const packageManagers = ['npm', 'pnpm', 'yarn', 'bun'] as const; +-/** +- * @param workingDirectory +- * @returns the install status of dependencies +- */ +-export async function suggestInstallingDependencies( +- workingDirectory: string, +-): Promise<'installed' | 'skipped'> { +- let selectedPm = await detectPackageManager(workingDirectory); +- +- if (!selectedPm) { +- selectedPm = await selectPrompt( ++ if (!pm) { ++ pm = await selectPrompt( + 'Which package manager do you want to install dependencies with?', + undefined, + [ +@@ -44,14 +33,26 @@ export async function suggestInstallingDependencies( + ); + } +- if (!selectedPm || !COMMANDS[selectedPm]) { ++ return pm; ++} ++ +/** ++ * @param packageManager + * @param workingDirectory -+ * @returns the package manager ++ * @returns the install status of dependencies + */ -+export async function detectPackageManager( ++export async function suggestInstallingDependencies( ++ packageManager: PackageManager | undefined, + workingDirectory: string, -+): Promise { -+ const detectedPm = await detect({ cwd: workingDirectory }); -+ const pm = normalizePackageManager(detectedPm.agent); -+ return pm; -+} -+ - /** - * @param workingDirectory - * @returns the install status of dependencies -@@ -14,22 +26,23 @@ const packageManagers = ['npm', 'pnpm', 'yarn', 'bun'] as const; - export async function suggestInstallingDependencies( - workingDirectory: string, - ): Promise<'installed' | 'skipped'> { -- const detectedPm = await detect({ cwd: workingDirectory }); -- let selectedPm = detectedPm.agent; -- -- selectedPm ??= await selectPrompt( -- 'Which package manager do you want to install dependencies with?', -- undefined, -- [ -- { -- label: 'None', -- value: undefined, -- }, -- ...packageManagers.map((x) => { -- return { label: x, value: x as PackageManager }; -- }), -- ], -- ); -+ let selectedPm = await detectPackageManager(workingDirectory); -+ -+ if (!selectedPm) { -+ selectedPm = await selectPrompt( -+ 'Which package manager do you want to install dependencies with?', -+ undefined, -+ [ -+ { -+ label: 'None', -+ value: undefined, -+ }, -+ ...packageManagers.map((x) => { -+ return { label: x, value: x }; -+ }), -+ ], -+ ); -+ } - - if (!selectedPm || !COMMANDS[selectedPm]) { ++): Promise<'installed' | 'skipped'> { ++ if (!packageManager || !COMMANDS[packageManager]) { return 'skipped'; + } + + const loadingSpinner = spinner(); + loadingSpinner.start('Installing dependencies...'); + +- const installCommand = COMMANDS[selectedPm].install; ++ const installCommand = COMMANDS[packageManager].install; + const [pm, install] = installCommand.split(' '); + await installDependencies(pm, [install], workingDirectory); + diff --git a/packages/core/utils/workspace.ts.rej b/packages/core/utils/workspace.ts.rej index dcd0c4ab..a1c6affe 100644 --- a/packages/core/utils/workspace.ts.rej +++ b/packages/core/utils/workspace.ts.rej @@ -1,17 +1,19 @@ diff a/packages/core/utils/workspace.ts b/packages/core/utils/workspace.ts (rejected hunks) -@@ -5,6 +5,7 @@ import { getJsAstEditor } from '@svelte-add/ast-manipulation'; +@@ -5,7 +5,7 @@ import { getJsAstEditor } from '@svelte-add/ast-manipulation'; import type { OptionDefinition, OptionValues, Question } from '../adder/options.js'; import { remoteControl } from '../internal.js'; import path from 'path'; -+import { detectPackageManager } from './dependencies.js'; +-import { detectPackageManager } from './dependencies.js'; ++import { selectPackageManager, type PackageManager } from './dependencies.js'; export type PrettierData = { installed: boolean; -@@ -46,6 +48,7 @@ export function createEmptyWorkspace(): Workspace +@@ -48,7 +48,7 @@ export function createEmptyWorkspace(): Workspace routesDirectory: 'src/routes', libDirectory: 'src/lib', }, -+ packageManager: '', +- packageManager: '', ++ packageManager: undefined, } as Workspace; } From 4fc52f0affa3633b2c9aa9513a106337c6a07d55 Mon Sep 17 00:00:00 2001 From: Matthew Stibbard Date: Wed, 4 Sep 2024 20:58:47 +1000 Subject: [PATCH 16/76] revert script execution experiment --- adders/supabase/config/adder.ts | 60 ++----------- packages/core/adder/config.ts | 7 -- packages/core/adder/config.ts.rej | 12 ++- packages/core/adder/execute.ts.rej | 29 +++++- packages/core/adder/nextSteps.ts.rej | 25 +----- packages/core/files/processors.ts | 28 ------ packages/core/files/processors.ts.rej | 13 ++- packages/core/utils/create-project.ts.rej | 4 +- packages/core/utils/dependencies.ts.rej | 105 +++++++++++++--------- packages/core/utils/workspace.ts.rej | 10 +-- 10 files changed, 118 insertions(+), 175 deletions(-) diff --git a/adders/supabase/config/adder.ts b/adders/supabase/config/adder.ts index 5644ca3b..1f0572bb 100644 --- a/adders/supabase/config/adder.ts +++ b/adders/supabase/config/adder.ts @@ -27,13 +27,6 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge condition: ({ options }) => options.cli, }, ], - scripts: [ - { - description: 'Supabase CLI initialization', - args: ['supabase', 'init', '--with-intellij-settings false', '--with-vscode-settings false'], - condition: ({ options }) => options.cli, - }, - ], files: [ { name: () => `.env`, @@ -564,49 +557,8 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge }, condition: ({ options }) => options.demo && options.cli, }, - { - name: () => './supabase/config.toml', - contentType: 'text', - content: ({ content }) => { - content = content.replace('"http://127.0.0.1:3000"', '"http://localhost:5173"'); - content = content.replace('"https://127.0.0.1:3000"', '"https://localhost:5173/*"'); - content = content.replace('enable_confirmations = false', 'enable_confirmations = true'); - - content = appendContent( - content, - dedent` - \n# Custom email confirmation template - [auth.email.template.confirmation] - subject = "Confirm Your Signup" - content_path = "./supabase/templates/confirmation.html" - `, - ); - - return content; - }, - condition: ({ options }) => options.cli, - }, - { - name: () => './supabase/templates/confirmation.html', - contentType: 'text', - content: () => { - return dedent` - - -

Confirm your signup

-

Follow this link to confirm your user:

-

Confirm your email

- - - `; - }, - condition: ({ options }) => options.cli, - }, ], - nextSteps: ({ options, packageManager }) => { + nextSteps: ({ options }) => { const steps = [ 'Visit the Supabase docs: https://supabase.com/docs', 'Update the authGuard server hook function with your protected routes', @@ -616,9 +568,9 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge steps.push( dedent`Local development environment: - 1. Initialize the local development environment: ${colors.yellow(`${packageManager} supabase init`)} - 2. Start the local development services: ${colors.yellow(`${packageManager} supabase start`)}. This may take a while the first time you run it - 3. Update ${colors.green('./supabase/config.toml')} [auth] section \`site_url\` and \`additional_redirect_urls\` to use port 5173 + 1. Initialize the local development environment (E.g., ${colors.yellow(`npx supabase init`)} or ${colors.yellow(`pnpm supabase init`)}) + 2. Start the local development services (E.g., ${colors.yellow(`npx supabase start`)} or ${colors.yellow(`pnpm supabase start`)}) + 3. Update ${colors.green('./supabase/config.toml')} [auth] section \`site_url\` and \`additional_redirect_urls\` to use port http://localhost:5173 4. Depending on your Auth selections, you may need to create local email templates and update ${colors.green('./supabase/config.toml')} `, ); @@ -661,12 +613,12 @@ function generateEnvFileContent({ content, options }: TextFileEditorArgs>>>>>> e647284 (add packageManager to nextSteps data object) @@ -52,12 +51,6 @@ export type PackageDefinition = { condition?: ConditionDefinition; }; -export type Scripts = { - description: string; - args: string[]; - condition?: ConditionDefinition; -}; - export type BaseAdderConfig = { metadata: AdderConfigMetadata; options: Args; diff --git a/packages/core/adder/config.ts.rej b/packages/core/adder/config.ts.rej index 1e757056..fd4b7d85 100644 --- a/packages/core/adder/config.ts.rej +++ b/packages/core/adder/config.ts.rej @@ -1,10 +1,16 @@ diff a/packages/core/adder/config.ts b/packages/core/adder/config.ts (rejected hunks) -@@ -61,7 +61,7 @@ export type BaseAdderConfig = { +@@ -61,14 +54,12 @@ export type BaseAdderConfig = { export type InlineAdderConfig = BaseAdderConfig & { integrationType: 'inline'; packages: PackageDefinition[]; -- scripts: Scripts[]; -+ scripts?: Scripts[]; +- scripts?: Scripts[]; files: FileTypes[]; nextSteps?: (data: { options: OptionValues; + cwd: string; + colors: Colors; + docs: string | undefined; +- packageManager: PackageManager; + }) => string[]; + installHook?: (workspace: Workspace) => Promise; + uninstallHook?: (workspace: Workspace) => Promise; diff --git a/packages/core/adder/execute.ts.rej b/packages/core/adder/execute.ts.rej index 08d375e7..e46694e2 100644 --- a/packages/core/adder/execute.ts.rej +++ b/packages/core/adder/execute.ts.rej @@ -1,18 +1,39 @@ diff a/packages/core/adder/execute.ts b/packages/core/adder/execute.ts (rejected hunks) +@@ -3,7 +3,7 @@ import * as pc from 'picocolors'; + import { serializeJson } from '@svelte-add/ast-tooling'; + import { commonFilePaths, format, writeFile } from '../files/utils.js'; + import { type ProjectType, createProject, detectSvelteDirectory } from '../utils/create-project.js'; +-import { createOrUpdateFiles, executeScripts } from '../files/processors.js'; ++import { createOrUpdateFiles } from '../files/processors.js'; + import { getPackageJson } from '../utils/common.js'; + import { + type Workspace, @@ -28,7 +28,7 @@ import type { InlineAdderConfig, } from './config.js'; import type { RemoteControlOptions } from './remoteControl.js'; --import { suggestInstallingDependencies } from '../utils/dependencies.js'; -+import { suggestInstallingDependencies, type PackageManager } from '../utils/dependencies.js'; +-import { suggestInstallingDependencies, type PackageManager } from '../utils/dependencies.js'; ++import { suggestInstallingDependencies } from '../utils/dependencies.js'; import { validatePreconditions } from './preconditions.js'; import { endPrompts, startPrompts } from '../utils/prompts.js'; import { checkPostconditions, printUnmetPostconditions } from './postconditions.js'; -@@ -102,6 +103,7 @@ export async function executeAdders( +@@ -103,7 +102,6 @@ export async function executeAdders( commonCliOptions, cliOptionsByAdderId, selectAddersToApply, -+ packageManager: undefined, +- packageManager: undefined, }; await executePlan(executionPlan, executingAdder, adderDetails, remoteControlOptions); +@@ -291,11 +281,6 @@ async function processInlineAdder( + isInstall: boolean, + ) { + const pkgPath = await installPackages(config, workspace); +- +- if (config.scripts && config.scripts.length > 0) { +- await executeScripts(config.scripts, workspace); +- } +- + const updatedOrCreatedFiles = await createOrUpdateFiles(config.files, workspace); + await runHooks(config, workspace, isInstall); + diff --git a/packages/core/adder/nextSteps.ts.rej b/packages/core/adder/nextSteps.ts.rej index 9be23483..a9e0d1ee 100644 --- a/packages/core/adder/nextSteps.ts.rej +++ b/packages/core/adder/nextSteps.ts.rej @@ -1,30 +1,9 @@ diff a/packages/core/adder/nextSteps.ts b/packages/core/adder/nextSteps.ts (rejected hunks) -@@ -1,18 +1,14 @@ --import { detectPackageManager } from '../utils/dependencies'; - import { messagePrompt } from '../utils/prompts'; - import type { InlineAdderConfig } from './config'; - import type { AdderDetails, AddersExecutionPlan } from './execute'; - import type { OptionDefinition, OptionValues } from './options'; - import pc from 'picocolors'; - --export async function displayNextSteps( -+export function displayNextSteps( - adderDetails: AdderDetails[], - multipleAdders: boolean, - executionPlan: AddersExecutionPlan, - ) { -- const packageManager = await detectPackageManager(executionPlan.workingDirectory); -- if (!packageManager) throw new Error('Unable to detect package manager'); -- - const allAddersMessage = adderDetails - .filter((x) => x.config.integrationType == 'inline' && x.config.nextSteps) - .map((x) => x.config as InlineAdderConfig) -@@ -32,7 +28,7 @@ export async function displayNextSteps( +@@ -28,7 +28,6 @@ export function displayNextSteps( cwd: executionPlan.workingDirectory, colors: pc, docs: x.metadata.website?.documentation, -- packageManager, -+ packageManager: executionPlan.packageManager ?? 'pnpm', +- packageManager: executionPlan.packageManager ?? 'pnpm', }); adderMessage += `- ${adderNextSteps.join('\n- ')}`; return adderMessage; diff --git a/packages/core/files/processors.ts b/packages/core/files/processors.ts index ca6746cc..7f430cb7 100644 --- a/packages/core/files/processors.ts +++ b/packages/core/files/processors.ts @@ -71,34 +71,6 @@ type BaseFile = { export type FileType = BaseFile & ParsedFile; -export async function executeScripts( - scripts: Scripts[], - workspace: Workspace, -): Promise { - const scriptsExecuted = []; - - if (scripts.length === 0) return []; - if (!workspace.packageManager) return []; - - const installCommand = COMMANDS[workspace.packageManager].install; - await installDependencies(workspace.packageManager, [installCommand], workspace.cwd); - - for (const script of scripts) { - if (script.condition && !script.condition(workspace)) { - continue; - } - try { - await executeCli(workspace.packageManager, script.args, workspace.cwd); - } catch (error) { - const typedError = error as Error; - throw new Error('Failed to execute package scripts: ' + typedError.message); - } - scriptsExecuted.push(script.description); - } - - return scriptsExecuted; -} - /** * @param files * @param workspace diff --git a/packages/core/files/processors.ts.rej b/packages/core/files/processors.ts.rej index 04b2b0e8..49984a68 100644 --- a/packages/core/files/processors.ts.rej +++ b/packages/core/files/processors.ts.rej @@ -1,10 +1,15 @@ diff a/packages/core/files/processors.ts b/packages/core/files/processors.ts (rejected hunks) -@@ -24,6 +24,8 @@ import type { ConditionDefinition, Scripts } from '../adder/config.js'; +@@ -20,12 +20,9 @@ import { + serializeSvelteFile, + } from '@svelte-add/ast-tooling'; + import { fileExistsWorkspace, readFile, writeFile } from './utils.js'; +-import type { ConditionDefinition, Scripts } from '../adder/config.js'; ++import type { ConditionDefinition } from '../adder/config.js'; import type { OptionDefinition } from '../adder/options.js'; import type { Workspace } from '../utils/workspace.js'; - import { executeCli } from '../utils/cli.js'; -+import { COMMANDS } from 'package-manager-detector/agents'; -+import { installDependencies } from '../utils/dependencies.js'; +-import { executeCli } from '../utils/cli.js'; +-import { COMMANDS } from 'package-manager-detector/agents'; +-import { installDependencies } from '../utils/dependencies.js'; export type BaseFile = { name: (options: Workspace) => string; diff --git a/packages/core/utils/create-project.ts.rej b/packages/core/utils/create-project.ts.rej index 7cea212d..85334813 100644 --- a/packages/core/utils/create-project.ts.rej +++ b/packages/core/utils/create-project.ts.rej @@ -1,8 +1,8 @@ diff a/packages/core/utils/create-project.ts b/packages/core/utils/create-project.ts (rejected hunks) -@@ -119,5 +127,6 @@ export async function createProject(cwd: string, supportKit: boolean, supportSve +@@ -127,6 +119,5 @@ export async function createProject(cwd: string, supportKit: boolean, supportSve return { projectCreated: true, directory: path.join(process.cwd(), directory), -+ packageManager, +- packageManager, }; } diff --git a/packages/core/utils/dependencies.ts.rej b/packages/core/utils/dependencies.ts.rej index 4bc457e2..451a09a7 100644 --- a/packages/core/utils/dependencies.ts.rej +++ b/packages/core/utils/dependencies.ts.rej @@ -1,60 +1,77 @@ diff a/packages/core/utils/dependencies.ts b/packages/core/utils/dependencies.ts (rejected hunks) -@@ -11,25 +11,14 @@ const packageManagers = ['npm', 'pnpm', 'yarn', 'bun'] as const; - * @param workingDirectory - * @returns the package manager - */ --export async function detectPackageManager( -+export async function selectPackageManager( - workingDirectory: string, - ): Promise { - const detectedPm = await detect({ cwd: workingDirectory }); -- const pm = normalizePackageManager(detectedPm.agent); -- return pm; --} -+ let pm = normalizePackageManager(detectedPm.agent); +@@ -4,55 +4,41 @@ import { COMMANDS } from 'package-manager-detector/agents'; + import { spinner } from '@svelte-add/clack-prompts'; + import { executeCli } from './cli.js'; --/** +-export type PackageManager = (typeof packageManagers)[number]; ++type PackageManager = (typeof packageManagers)[number] | undefined; + const packageManagers = ['npm', 'pnpm', 'yarn', 'bun'] as const; + + /** - * @param workingDirectory -- * @returns the install status of dependencies +- * @returns the package manager - */ --export async function suggestInstallingDependencies( +-export async function selectPackageManager( - workingDirectory: string, --): Promise<'installed' | 'skipped'> { -- let selectedPm = await detectPackageManager(workingDirectory); +-): Promise { +- const detectedPm = await detect({ cwd: workingDirectory }); +- let pm = normalizePackageManager(detectedPm.agent); - -- if (!selectedPm) { -- selectedPm = await selectPrompt( -+ if (!pm) { -+ pm = await selectPrompt( - 'Which package manager do you want to install dependencies with?', - undefined, - [ -@@ -44,14 +33,26 @@ export async function suggestInstallingDependencies( - ); - } - -- if (!selectedPm || !COMMANDS[selectedPm]) { -+ return pm; -+} +- if (!pm) { +- pm = await selectPrompt( +- 'Which package manager do you want to install dependencies with?', +- undefined, +- [ +- { +- label: 'None', +- value: undefined, +- }, +- ...packageManagers.map((x) => { +- return { label: x, value: x }; +- }), +- ], +- ); +- } +- +- return pm; +-} +- +-/** +- * @param packageManager + * @param workingDirectory + * @returns the install status of dependencies + */ + export async function suggestInstallingDependencies( +- packageManager: PackageManager | undefined, + workingDirectory: string, + ): Promise<'installed' | 'skipped'> { +- if (!packageManager || !COMMANDS[packageManager]) { ++ const detectedPm = await detect({ cwd: workingDirectory }); ++ let selectedPm = detectedPm.agent; ++ ++ selectedPm ??= await selectPrompt( ++ 'Which package manager do you want to install dependencies with?', ++ undefined, ++ [ ++ { ++ label: 'None', ++ value: undefined, ++ }, ++ ...packageManagers.map((x) => { ++ return { label: x, value: x as PackageManager }; ++ }), ++ ], ++ ); + -+/** -+ * @param packageManager -+ * @param workingDirectory -+ * @returns the install status of dependencies -+ */ -+export async function suggestInstallingDependencies( -+ packageManager: PackageManager | undefined, -+ workingDirectory: string, -+): Promise<'installed' | 'skipped'> { -+ if (!packageManager || !COMMANDS[packageManager]) { ++ if (!selectedPm || !COMMANDS[selectedPm]) { return 'skipped'; } const loadingSpinner = spinner(); loadingSpinner.start('Installing dependencies...'); -- const installCommand = COMMANDS[selectedPm].install; -+ const installCommand = COMMANDS[packageManager].install; +- const installCommand = COMMANDS[packageManager].install; ++ const installCommand = COMMANDS[selectedPm].install; const [pm, install] = installCommand.split(' '); await installDependencies(pm, [install], workingDirectory); diff --git a/packages/core/utils/workspace.ts.rej b/packages/core/utils/workspace.ts.rej index a1c6affe..ebd55961 100644 --- a/packages/core/utils/workspace.ts.rej +++ b/packages/core/utils/workspace.ts.rej @@ -1,19 +1,17 @@ diff a/packages/core/utils/workspace.ts b/packages/core/utils/workspace.ts (rejected hunks) -@@ -5,7 +5,7 @@ import { getJsAstEditor } from '@svelte-add/ast-manipulation'; +@@ -5,7 +5,6 @@ import { getJsAstEditor } from '@svelte-add/ast-manipulation'; import type { OptionDefinition, OptionValues, Question } from '../adder/options.js'; import { remoteControl } from '../internal.js'; import path from 'path'; --import { detectPackageManager } from './dependencies.js'; -+import { selectPackageManager, type PackageManager } from './dependencies.js'; +-import { selectPackageManager, type PackageManager } from './dependencies.js'; export type PrettierData = { installed: boolean; -@@ -48,7 +48,7 @@ export function createEmptyWorkspace(): Workspace +@@ -48,7 +46,6 @@ export function createEmptyWorkspace(): Workspace routesDirectory: 'src/routes', libDirectory: 'src/lib', }, -- packageManager: '', -+ packageManager: undefined, +- packageManager: undefined, } as Workspace; } From 5ce1f9c9822ee6910c8b3dadcec212d7cac91c46 Mon Sep 17 00:00:00 2001 From: Matthew Stibbard Date: Thu, 5 Sep 2024 14:45:26 +1000 Subject: [PATCH 17/76] better script experiment --- adders/supabase/config/adder.ts | 72 +++++++++++++++++++---- packages/core/adder/config.ts | 8 +++ packages/core/adder/config.ts.rej | 6 +- packages/core/adder/execute.ts.rej | 44 +++++++------- packages/core/adder/nextSteps.ts.rej | 4 +- packages/core/utils/dependencies.ts.rej | 76 +++++-------------------- packages/core/utils/workspace.ts.rej | 19 +++++-- 7 files changed, 124 insertions(+), 105 deletions(-) diff --git a/adders/supabase/config/adder.ts b/adders/supabase/config/adder.ts index 1f0572bb..aeee2fea 100644 --- a/adders/supabase/config/adder.ts +++ b/adders/supabase/config/adder.ts @@ -27,6 +27,14 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge condition: ({ options }) => options.cli, }, ], + scripts: [ + { + description: 'Supabase CLI initialization', + args: ['supabase', 'init', '--with-intellij-settings false', '--with-vscode-settings false'], + type: 'dependency', + condition: ({ options }) => options.cli, + }, + ], files: [ { name: () => `.env`, @@ -385,6 +393,47 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge }, condition: ({ options }) => options.helpers, }, + { + name: () => './supabase/config.toml', + contentType: 'text', + content: ({ content }) => { + content = content.replace('"http://127.0.0.1:3000"', '"http://localhost:5173"'); + content = content.replace('"https://127.0.0.1:3000"', '"https://localhost:5173/*"'); + content = content.replace('enable_confirmations = false', 'enable_confirmations = true'); + + content = appendContent( + content, + dedent` + \n# Custom email confirmation template + [auth.email.template.confirmation] + subject = "Confirm Your Signup" + content_path = "./supabase/templates/confirmation.html" + `, + ); + + return content; + }, + condition: ({ options }) => options.cli, + }, + { + name: () => './supabase/templates/confirmation.html', + contentType: 'text', + content: () => { + return dedent` + + +

Confirm your signup

+

Follow this link to confirm your user:

+

Confirm your email

+ + + `; + }, + condition: ({ options }) => options.cli, + }, // Demo routes { name: ({ kit }) => `${kit.routesDirectory}/+page.svelte`, @@ -558,22 +607,21 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge condition: ({ options }) => options.demo && options.cli, }, ], - nextSteps: ({ options }) => { + nextSteps: ({ options, packageManager }) => { + let command: string; + if (!packageManager || packageManager === 'npm') { + command = 'npx'; + } else { + command = packageManager; + } + const steps = [ 'Visit the Supabase docs: https://supabase.com/docs', 'Update the authGuard server hook function with your protected routes', ]; if (options.cli) { - steps.push( - dedent`Local development environment: - - 1. Initialize the local development environment (E.g., ${colors.yellow(`npx supabase init`)} or ${colors.yellow(`pnpm supabase init`)}) - 2. Start the local development services (E.g., ${colors.yellow(`npx supabase start`)} or ${colors.yellow(`pnpm supabase start`)}) - 3. Update ${colors.green('./supabase/config.toml')} [auth] section \`site_url\` and \`additional_redirect_urls\` to use port http://localhost:5173 - 4. Depending on your Auth selections, you may need to create local email templates and update ${colors.green('./supabase/config.toml')} - `, - ); + steps.push(`Start local Supabase services: ${colors.yellow(`${command} supabase start`)}`); } return steps; @@ -613,12 +661,12 @@ function generateEnvFileContent({ content, options }: TextFileEditorArgs>>>>>> e647284 (add packageManager to nextSteps data object) @@ -51,6 +52,13 @@ export type PackageDefinition = { condition?: ConditionDefinition; }; +export type Scripts = { + description: string; + args: string[]; + type: 'dependency' | 'external'; + condition?: ConditionDefinition; +}; + export type BaseAdderConfig = { metadata: AdderConfigMetadata; options: Args; diff --git a/packages/core/adder/config.ts.rej b/packages/core/adder/config.ts.rej index fd4b7d85..d80df24c 100644 --- a/packages/core/adder/config.ts.rej +++ b/packages/core/adder/config.ts.rej @@ -1,16 +1,16 @@ diff a/packages/core/adder/config.ts b/packages/core/adder/config.ts (rejected hunks) -@@ -61,14 +54,12 @@ export type BaseAdderConfig = { +@@ -54,12 +62,14 @@ export type BaseAdderConfig = { export type InlineAdderConfig = BaseAdderConfig & { integrationType: 'inline'; packages: PackageDefinition[]; -- scripts?: Scripts[]; ++ scripts?: Scripts[]; files: FileTypes[]; nextSteps?: (data: { options: OptionValues; cwd: string; colors: Colors; docs: string | undefined; -- packageManager: PackageManager; ++ packageManager: PackageManager; }) => string[]; installHook?: (workspace: Workspace) => Promise; uninstallHook?: (workspace: Workspace) => Promise; diff --git a/packages/core/adder/execute.ts.rej b/packages/core/adder/execute.ts.rej index e46694e2..f2472f81 100644 --- a/packages/core/adder/execute.ts.rej +++ b/packages/core/adder/execute.ts.rej @@ -1,39 +1,41 @@ diff a/packages/core/adder/execute.ts b/packages/core/adder/execute.ts (rejected hunks) -@@ -3,7 +3,7 @@ import * as pc from 'picocolors'; - import { serializeJson } from '@svelte-add/ast-tooling'; - import { commonFilePaths, format, writeFile } from '../files/utils.js'; - import { type ProjectType, createProject, detectSvelteDirectory } from '../utils/create-project.js'; --import { createOrUpdateFiles, executeScripts } from '../files/processors.js'; -+import { createOrUpdateFiles } from '../files/processors.js'; - import { getPackageJson } from '../utils/common.js'; - import { - type Workspace, -@@ -28,7 +28,7 @@ import type { +@@ -26,9 +26,15 @@ import type { + AdderConfigMetadata, + ExternalAdderConfig, InlineAdderConfig, ++ Scripts, } from './config.js'; import type { RemoteControlOptions } from './remoteControl.js'; --import { suggestInstallingDependencies, type PackageManager } from '../utils/dependencies.js'; -+import { suggestInstallingDependencies } from '../utils/dependencies.js'; +-import { suggestInstallingDependencies } from '../utils/dependencies.js'; ++import { ++ getPackageManager, ++ installDependencies, ++ suggestInstallingDependencies, ++ type PackageManager, ++} from '../utils/dependencies.js'; import { validatePreconditions } from './preconditions.js'; import { endPrompts, startPrompts } from '../utils/prompts.js'; import { checkPostconditions, printUnmetPostconditions } from './postconditions.js'; -@@ -103,7 +102,6 @@ export async function executeAdders( +@@ -95,12 +102,14 @@ export async function executeAdders( + workingDirectory = await detectSvelteDirectory(workingDirectory); + const createProject = workingDirectory == null; + if (!workingDirectory) workingDirectory = process.cwd(); ++ const packageManager = getPackageManager(); + + const executionPlan: AddersExecutionPlan = { + workingDirectory, + createProject, commonCliOptions, cliOptionsByAdderId, ++ packageManager, selectAddersToApply, -- packageManager: undefined, }; - await executePlan(executionPlan, executingAdder, adderDetails, remoteControlOptions); -@@ -291,11 +281,6 @@ async function processInlineAdder( +@@ -281,6 +305,7 @@ async function processInlineAdder( isInstall: boolean, ) { const pkgPath = await installPackages(config, workspace); -- -- if (config.scripts && config.scripts.length > 0) { -- await executeScripts(config.scripts, workspace); -- } -- ++ if (config.scripts) await runScripts(config.scripts, workspace); const updatedOrCreatedFiles = await createOrUpdateFiles(config.files, workspace); await runHooks(config, workspace, isInstall); diff --git a/packages/core/adder/nextSteps.ts.rej b/packages/core/adder/nextSteps.ts.rej index a9e0d1ee..aa9bf1cd 100644 --- a/packages/core/adder/nextSteps.ts.rej +++ b/packages/core/adder/nextSteps.ts.rej @@ -1,9 +1,9 @@ diff a/packages/core/adder/nextSteps.ts b/packages/core/adder/nextSteps.ts (rejected hunks) -@@ -28,7 +28,6 @@ export function displayNextSteps( +@@ -28,6 +28,7 @@ export function displayNextSteps( cwd: executionPlan.workingDirectory, colors: pc, docs: x.metadata.website?.documentation, -- packageManager: executionPlan.packageManager ?? 'pnpm', ++ packageManager: executionPlan.packageManager, }); adderMessage += `- ${adderNextSteps.join('\n- ')}`; return adderMessage; diff --git a/packages/core/utils/dependencies.ts.rej b/packages/core/utils/dependencies.ts.rej index 451a09a7..47142d11 100644 --- a/packages/core/utils/dependencies.ts.rej +++ b/packages/core/utils/dependencies.ts.rej @@ -1,77 +1,27 @@ diff a/packages/core/utils/dependencies.ts b/packages/core/utils/dependencies.ts (rejected hunks) -@@ -4,55 +4,41 @@ import { COMMANDS } from 'package-manager-detector/agents'; +@@ -1,21 +1,21 @@ + import { selectPrompt } from './prompts'; +-import { detect } from 'package-manager-detector'; + import { COMMANDS } from 'package-manager-detector/agents'; import { spinner } from '@svelte-add/clack-prompts'; import { executeCli } from './cli.js'; --export type PackageManager = (typeof packageManagers)[number]; -+type PackageManager = (typeof packageManagers)[number] | undefined; +-type PackageManager = (typeof packageManagers)[number] | undefined; ++export type PackageManager = (typeof packageManagers)[number] | undefined; const packageManagers = ['npm', 'pnpm', 'yarn', 'bun'] as const; /** -- * @param workingDirectory -- * @returns the package manager -- */ --export async function selectPackageManager( -- workingDirectory: string, --): Promise { -- const detectedPm = await detect({ cwd: workingDirectory }); -- let pm = normalizePackageManager(detectedPm.agent); -- -- if (!pm) { -- pm = await selectPrompt( -- 'Which package manager do you want to install dependencies with?', -- undefined, -- [ -- { -- label: 'None', -- value: undefined, -- }, -- ...packageManagers.map((x) => { -- return { label: x, value: x }; -- }), -- ], -- ); -- } -- -- return pm; --} -- --/** -- * @param packageManager ++ * @param packageManager * @param workingDirectory * @returns the install status of dependencies */ export async function suggestInstallingDependencies( -- packageManager: PackageManager | undefined, ++ packageManager: PackageManager, workingDirectory: string, ): Promise<'installed' | 'skipped'> { -- if (!packageManager || !COMMANDS[packageManager]) { -+ const detectedPm = await detect({ cwd: workingDirectory }); -+ let selectedPm = detectedPm.agent; -+ -+ selectedPm ??= await selectPrompt( -+ 'Which package manager do you want to install dependencies with?', -+ undefined, -+ [ -+ { -+ label: 'None', -+ value: undefined, -+ }, -+ ...packageManagers.map((x) => { -+ return { label: x, value: x as PackageManager }; -+ }), -+ ], -+ ); -+ -+ if (!selectedPm || !COMMANDS[selectedPm]) { - return 'skipped'; - } - - const loadingSpinner = spinner(); - loadingSpinner.start('Installing dependencies...'); - -- const installCommand = COMMANDS[packageManager].install; -+ const installCommand = COMMANDS[selectedPm].install; - const [pm, install] = installCommand.split(' '); - await installDependencies(pm, [install], workingDirectory); +- const detectedPm = await detect({ cwd: workingDirectory }); +- let selectedPm = detectedPm.agent; ++ let selectedPm = packageManager; + selectedPm ??= await selectPrompt( + 'Which package manager do you want to install dependencies with?', diff --git a/packages/core/utils/workspace.ts.rej b/packages/core/utils/workspace.ts.rej index ebd55961..f95e9eb1 100644 --- a/packages/core/utils/workspace.ts.rej +++ b/packages/core/utils/workspace.ts.rej @@ -1,17 +1,28 @@ diff a/packages/core/utils/workspace.ts b/packages/core/utils/workspace.ts (rejected hunks) -@@ -5,7 +5,6 @@ import { getJsAstEditor } from '@svelte-add/ast-manipulation'; +@@ -5,6 +5,7 @@ import { getJsAstEditor } from '@svelte-add/ast-manipulation'; import type { OptionDefinition, OptionValues, Question } from '../adder/options.js'; import { remoteControl } from '../internal.js'; import path from 'path'; --import { selectPackageManager, type PackageManager } from './dependencies.js'; ++import type { PackageManager } from './dependencies.js'; export type PrettierData = { installed: boolean; -@@ -48,7 +46,6 @@ export function createEmptyWorkspace(): Workspace +@@ -46,6 +48,7 @@ export function createEmptyWorkspace(): Workspace routesDirectory: 'src/routes', libDirectory: 'src/lib', }, -- packageManager: undefined, ++ packageManager: undefined, } as Workspace; } +@@ -73,8 +76,10 @@ export function addPropertyToWorkspaceOption( + export async function populateWorkspaceDetails( + workspace: WorkspaceWithoutExplicitArgs, + workingDirectory: string, ++ packageManager: PackageManager, + ) { + workspace.cwd = workingDirectory; ++ workspace.packageManager = packageManager; + + const tsConfigFileName = 'tsconfig.json'; + const viteConfigFileName = 'vite.config.ts'; From 40f9484a675276f5b23fb8f10459b59c013ed912 Mon Sep 17 00:00:00 2001 From: Matthew Stibbard Date: Thu, 5 Sep 2024 23:39:31 +1000 Subject: [PATCH 18/76] multiselect auth option: basic, magic link, oauth. better nextSteps --- adders/supabase/config/adder.ts | 378 ++++++++++++++++++++++------- adders/supabase/config/options.ts | 40 ++- packages/core/adder/options.ts | 281 +++++++++++++++++++++ packages/core/adder/options.ts.rej | 25 ++ 4 files changed, 626 insertions(+), 98 deletions(-) create mode 100644 packages/core/adder/options.ts.rej diff --git a/adders/supabase/config/adder.ts b/adders/supabase/config/adder.ts index aeee2fea..2443c243 100644 --- a/adders/supabase/config/adder.ts +++ b/adders/supabase/config/adder.ts @@ -18,7 +18,12 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge integrationType: 'inline', packages: [ { name: '@supabase/supabase-js', version: '^2.45.3', dev: false }, - { name: '@supabase/ssr', version: '^0.5.1', dev: false }, + { + name: '@supabase/ssr', + version: '^0.5.1', + dev: false, + condition: ({ options }) => options.auth.length > 0, + }, // Local development CLI { name: 'supabase', @@ -49,15 +54,18 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge { name: ({ typescript }) => `./src/hooks.server.${typescript.installed ? 'ts' : 'js'}`, contentType: 'text', + condition: ({ options }) => options.auth.length > 0, content: ({ options, typescript }) => { + const isTs = typescript.installed; + return dedent` import { createServerClient } from '@supabase/ssr' - import {${typescript.installed ? ' type Handle,' : ''} redirect } from '@sveltejs/kit' + import {${isTs ? ' type Handle,' : ''} redirect } from '@sveltejs/kit' import { sequence } from '@sveltejs/kit/hooks' import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public' - const supabase${typescript.installed ? ': Handle' : ''} = async ({ event, resolve }) => { + const supabase${isTs ? ': Handle' : ''} = async ({ event, resolve }) => { event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { cookies: { getAll: () => event.cookies.getAll(), @@ -95,7 +103,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge }) } - const authGuard${typescript.installed ? ': Handle' : ''} = async ({ event, resolve }) => { + const authGuard${isTs ? ': Handle' : ''} = async ({ event, resolve }) => { const { session, user } = await event.locals.safeGetSession() event.locals.session = session event.locals.user = user @@ -117,23 +125,25 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge return resolve(event) } - export const handle${typescript.installed ? ': Handle' : ''} = sequence(supabase, authGuard) + export const handle${isTs ? ': Handle' : ''} = sequence(supabase, authGuard) `; }, }, { name: () => './src/app.d.ts', contentType: 'text', - condition: ({ typescript }) => typescript.installed, + condition: ({ options, typescript }) => typescript.installed && options.auth.length > 0, content: ({ options }) => { + const { cli, helpers } = options; + return dedent` import type { Session, SupabaseClient, User } from '@supabase/supabase-js' - ${options.cli && options.helpers ? `import type { Database } from '$lib/supabase-types'\n` : ''} + ${cli && helpers ? `import type { Database } from '$lib/supabase-types'\n` : ''} declare global { namespace App { // interface Error {} interface Locals { - supabase: SupabaseClient${options.cli && options.helpers ? `` : ''} + supabase: SupabaseClient${cli && helpers ? `` : ''} safeGetSession: () => Promise<{ session: Session | null; user: User | null }> session: Session | null user: User | null @@ -154,12 +164,15 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge name: ({ kit, typescript }) => `${kit.routesDirectory}/+layout.${typescript.installed ? 'ts' : 'js'}`, contentType: 'text', + condition: ({ options }) => options.auth.length > 0, content: ({ typescript }) => { + const isTs = typescript.installed; + return dedent` import { createBrowserClient, createServerClient, isBrowser } from '@supabase/ssr' import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public' - ${typescript.installed ? `import type { LayoutLoad } from './$types'\n` : ''} - export const load${typescript.installed ? ': LayoutLoad' : ''} = async ({ data, depends, fetch }) => { + ${isTs ? `import type { LayoutLoad } from './$types'\n` : ''} + export const load${isTs ? ': LayoutLoad' : ''} = async ({ data, depends, fetch }) => { depends('supabase:auth') const supabase = isBrowser() @@ -192,10 +205,13 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge name: ({ kit, typescript }) => `${kit.routesDirectory}/+layout.server.${typescript.installed ? 'ts' : 'js'}`, contentType: 'text', + condition: ({ options }) => options.auth.length > 0, content: ({ typescript }) => { + const isTs = typescript.installed; + return dedent` - ${typescript.installed ? `import type { LayoutServerLoad } from './$types'\n` : ''} - export const load${typescript.installed ? ': LayoutServerLoad' : ''} = async ({ locals: { session }, cookies }) => { + ${isTs ? `import type { LayoutServerLoad } from './$types'\n` : ''} + export const load${isTs ? ': LayoutServerLoad' : ''} = async ({ locals: { session }, cookies }) => { return { session, cookies: cookies.getAll(), @@ -207,6 +223,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge { name: ({ kit }) => `${kit.routesDirectory}/+layout.svelte`, contentType: 'text', + condition: ({ options }) => options.auth.length > 0, content: ({ typescript }) => { return dedent` @@ -235,35 +252,49 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge name: ({ kit, typescript }) => `${kit.routesDirectory}/auth/+page.server.${typescript.installed ? 'ts' : 'js'}`, contentType: 'text', + condition: ({ options }) => + options.auth.includes('basic') || options.auth.includes('magicLink'), content: ({ options, typescript }) => { + const isTs = typescript.installed; + const { auth, demo: isDemo } = options; + + const isBasic = auth.includes('basic'); + const isMagicLink = auth.includes('magicLink'); + return dedent` - import { redirect } from '@sveltejs/kit' - import { PUBLIC_BASE_URL } from '$env/static/public' - ${typescript.installed ? `import type { Actions } from './$types'\n` : ''} - export const actions${typescript.installed ? `: Actions` : ''} = { + ${ + isBasic + ? ` + import { redirect } from '@sveltejs/kit' + import { PUBLIC_BASE_URL } from '$env/static/public'` + : '' + } + ${isTs ? `import type { Actions } from './$types'\n` : ''} + export const actions${isTs ? `: Actions` : ''} = {${ + isBasic + ? ` signup: async ({ request, locals: { supabase } }) => { const formData = await request.formData() - const email = formData.get('email')${typescript.installed ? ' as string' : ''} - const password = formData.get('password')${typescript.installed ? ' as string' : ''} + const email = formData.get('email')${isTs ? ' as string' : ''} + const password = formData.get('password')${isTs ? ' as string' : ''} - const { error } = await supabase.auth.signUp(${ - options.demo + const { error } = await supabase.auth.signUp({${ + isDemo ? ` - { - email, - password, - options: { - emailRedirectTo: \`\${PUBLIC_BASE_URL}/private\`, - } - }` - : '{ email, password }' - }) + email, + password, + options: { + emailRedirectTo: \`\${PUBLIC_BASE_URL}/private\`, + } + })` + : ' email, password })' + } if (error) { console.error(error) return { message: 'Something went wrong, please try again.' } } else { ${ - options.demo + isDemo ? `// Redirect to local Inbucket for demo purposes redirect(303, \`http://localhost:54324/m/\${email}\`)` : `return { message: 'Sign up succeeded! Please check your email inbox.' }` @@ -272,8 +303,8 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge }, login: async ({ request, locals: { supabase } }) => { const formData = await request.formData() - const email = formData.get('email')${typescript.installed ? ' as string' : ''} - const password = formData.get('password')${typescript.installed ? ' as string' : ''} + const email = formData.get('email')${isTs ? ' as string' : ''} + const password = formData.get('password')${isTs ? ' as string' : ''} const { error } = await supabase.auth.signInWithPassword({ email, password }) if (error) { @@ -282,7 +313,25 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge } else { redirect(303, '/private') } - }, + },` + : '' + }${ + isMagicLink + ? ` + magic: async ({ request, locals: { supabase } }) => { + const formData = await request.formData() + const email = formData.get('email')${isTs ? ' as string' : ''} + + const { error } = await supabase.auth.signInWithOtp({ email }) + if (error) { + console.error(error) + return { message: 'Something went wrong, please try again.' } + } else { + return { message: 'Check your email inbox.' } + } + },` + : '' + } } `; }, @@ -290,30 +339,73 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge { name: ({ kit }) => `${kit.routesDirectory}/auth/+page.svelte`, contentType: 'text', - content: ({ typescript }) => { + condition: ({ options }) => options.auth.length > 0, + content: ({ options, typescript }) => { + const isTs = typescript.installed; + const { auth } = options; + + const isBasic = auth.includes('basic'); + const isMagicLink = auth.includes('magicLink'); + const isOAuth = auth.includes('oauth'); + return dedent` - -
+ ${ + isBasic || isMagicLink + ? ` + + ` + : '' + } + ${ + isBasic + ? ` - - -
- + + ` + : '' + } + ${isMagicLink ? '' : ''} + ${isBasic || isMagicLink ? `` : ''} + ${isOAuth ? `` : ''} + ${ + isBasic || isMagicLink + ? ` {#if form?.message}

{form.message}

- {/if} + {/if}` + : '' + } `; }, }, @@ -321,19 +413,23 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge name: ({ kit, typescript }) => `${kit.routesDirectory}/auth/confirm/+server.${typescript.installed ? 'ts' : 'js'}`, contentType: 'text', + condition: ({ options }) => + options.auth.includes('basic') || options.auth.includes('magicLink'), content: ({ typescript }) => { + const isTs = typescript.installed; + return dedent` import { error, redirect } from '@sveltejs/kit' import { PUBLIC_BASE_URL } from '$env/static/public' ${ - typescript.installed + isTs ? dedent`import type { EmailOtpType } from '@supabase/supabase-js' import type { RequestHandler } from './$types'\n` : '' } - export const GET${typescript.installed ? ': RequestHandler' : ''} = async ({ url, locals: { supabase } }) => { + export const GET${isTs ? ': RequestHandler' : ''} = async ({ url, locals: { supabase } }) => { const token_hash = url.searchParams.get('token_hash') - const type = url.searchParams.get('type')${typescript.installed ? ' as EmailOtpType | null' : ''} + const type = url.searchParams.get('type')${isTs ? ' as EmailOtpType | null' : ''} const next = url.searchParams.get('next') ?? \`\${PUBLIC_BASE_URL}/\` const redirectTo = new URL(next) @@ -352,18 +448,51 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge `; }, }, + { + name: ({ kit, typescript }) => + `${kit.routesDirectory}/auth/callback/+server.${typescript.installed ? 'ts' : 'js'}`, + contentType: 'text', + condition: ({ options }) => options.auth.includes('oauth'), + content: ({ typescript }) => { + const isTs = typescript.installed; + + return dedent` + import { error, redirect } from '@sveltejs/kit' + ${isTs ? `import type { RequestHandler } from './$types'` : ''} + + export const GET${isTs ? ': RequestHandler' : ''} = async ({ url, locals: { supabase } }) => { + const code = url.searchParams.get('code')${isTs ? ' as string' : ''} + const next = url.searchParams.get('next') ?? '/' + + if (code) { + const { error: authError } = await supabase.auth.exchangeCodeForSession(code) + if (authError) { + console.error(authError) + error(500, 'Something went wrong, please try again.') + } + } + + throw redirect(303, \`/\${next.slice(1)}\`) + } + `; + }, + }, { name: ({ kit, typescript }) => `${kit.libDirectory}/server/supabase-admin.${typescript.installed ? 'ts' : 'js'}`, contentType: 'text', + condition: ({ options }) => options.admin, content: ({ options, typescript }) => { + const isTs = typescript.installed; + const { cli: isCli, helpers: isHelpers } = options; + return dedent` import { PUBLIC_SUPABASE_URL } from '$env/static/public' import { SUPABASE_SERVICE_ROLE_KEY } from '$env/static/private' - ${typescript && options.cli && options.helpers ? `import type { Database } from '$lib/supabase-types'\n` : ''} + ${isTs && isCli && isHelpers ? `import type { Database } from '$lib/supabase-types'` : ''} import { createClient } from '@supabase/supabase-js' - export const supabaseAdmin = createClient${typescript && options.cli && options.helpers ? '' : ''}( + export const supabaseAdmin = createClient${isTs && isCli && isHelpers ? '' : ''}( PUBLIC_SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, { @@ -375,11 +504,11 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge ); `; }, - condition: ({ options }) => options.admin, }, { name: () => `package.json`, contentType: 'json', + condition: ({ options }) => options.helpers, content: ({ data, typescript }) => { data.scripts ??= {}; const scripts: Record = data.scripts; @@ -391,33 +520,46 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge 'supabase gen types typescript --local > src/lib/supabase-types.ts'; } }, - condition: ({ options }) => options.helpers, }, { name: () => './supabase/config.toml', contentType: 'text', - content: ({ content }) => { + condition: ({ options }) => options.cli, + content: ({ content, options }) => { content = content.replace('"http://127.0.0.1:3000"', '"http://localhost:5173"'); content = content.replace('"https://127.0.0.1:3000"', '"https://localhost:5173/*"'); - content = content.replace('enable_confirmations = false', 'enable_confirmations = true'); - - content = appendContent( - content, - dedent` - \n# Custom email confirmation template - [auth.email.template.confirmation] - subject = "Confirm Your Signup" - content_path = "./supabase/templates/confirmation.html" - `, - ); + + if (options.auth.includes('basic')) { + content = content.replace('enable_confirmations = false', 'enable_confirmations = true'); + content = appendContent( + content, + dedent` + \n# Custom email confirmation template + [auth.email.template.confirmation] + subject = "Confirm Your Signup" + content_path = "./supabase/templates/confirmation.html" + `, + ); + } + if (options.auth.includes('magicLink')) { + content = appendContent( + content, + dedent` + \n# Custom magic link template + [auth.email.template.magic_link] + subject = "Your Magic Link" + content_path = "./supabase/templates/magic_link.html" + `, + ); + } return content; }, - condition: ({ options }) => options.cli, }, { name: () => './supabase/templates/confirmation.html', contentType: 'text', + condition: ({ options }) => options.cli && options.auth.includes('basic'), content: () => { return dedent` @@ -432,12 +574,30 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge `; }, - condition: ({ options }) => options.cli, }, - // Demo routes + { + name: () => './supabase/templates/magic_link.html', + contentType: 'text', + condition: ({ options }) => options.cli && options.auth.includes('magicLink'), + content: () => { + return dedent` + + +

Follow this link to login:

+

Log in

+ + + `; + }, + }, + // Demo routes when user has selected Basic Auth { name: ({ kit }) => `${kit.routesDirectory}/+page.svelte`, contentType: 'text', + condition: ({ options }) => options.demo, content: ({ typescript }) => { return dedent` @@ -454,12 +614,12 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge `; }, - condition: ({ options }) => options.demo, }, { name: ({ kit, typescript }) => `${kit.routesDirectory}/private/+layout.server.${typescript.installed ? 'ts' : 'js'}`, contentType: 'text', + condition: ({ options }) => options.demo, content: () => { return dedent` /** @@ -469,11 +629,11 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge **/ `; }, - condition: ({ options }) => options.demo, }, { name: ({ kit }) => `${kit.routesDirectory}/private/+layout.svelte`, contentType: 'text', + condition: ({ options }) => options.demo, content: () => { return dedent` + +
+ + +
+ + {#if form?.message} +

{form.message}

+ {/if} + `; + }, + }, + { + name: ({ kit, typescript }) => + `${kit.routesDirectory}/auth/forgot-password/+page.server.${typescript.installed ? 'ts' : 'js'}`, + contentType: 'text', + condition: ({ options }) => options.auth.includes('basic'), + content: ({ typescript }) => { + const isTs = typescript.installed; + + return dedent` + import { PUBLIC_BASE_URL } from '$env/static/public' + ${isTs ? `import type { Actions } from './$types'` : ''} + + export const actions${isTs ? `: Actions` : ''} = { + default: async ({ request, locals: { supabase } }) => { + const formData = await request.formData() + const email = formData.get('email')${isTs ? ' as string' : ''} + + const { error } = await supabase.auth.resetPasswordForEmail( + email, + { redirectTo: \`\${PUBLIC_BASE_URL}/auth/reset-password\` } + ) + if (error) { + console.error(error) + return { message: 'Something went wrong, please try again.' } + } else { + return { message: 'Please check your email inbox.' } + } + }, + } + `; + }, + }, + { + name: ({ kit, typescript }) => + `${kit.routesDirectory}/auth/reset-password/+page.server.${typescript.installed ? 'ts' : 'js'}`, + contentType: 'text', + condition: ({ options }) => options.auth.includes('basic'), + content: ({ typescript }) => { + const isTs = typescript.installed; + + return dedent` + ${isTs ? `import type { Actions } from './$types'` : ''} + + export const actions${isTs ? `: Actions` : ''} = { + default: async ({ request, locals: { supabase } }) => { + const formData = await request.formData() + const password = formData.get('password')${isTs ? ' as string' : ''} + + const { error } = await supabase.auth.updateUser({ password }) + if (error) { + console.error(error) + return { message: 'Something went wrong, please try again.' } + } else { + return { message: 'Password has been reset' } + } + }, + } + `; + }, + }, + { + name: ({ kit }) => `${kit.routesDirectory}/auth/reset-password/+page.svelte`, + contentType: 'text', + condition: ({ options }) => options.auth.includes('basic'), + content: ({ typescript }) => { + const isTs = typescript.installed; + + return dedent` + + import { enhance } from '$app/forms' + + ${isTs ? `import type { ActionData } from './$types'` : ''} + + export let form${isTs ? ': ActionData' : ''} + + +
+ + +
+ + {#if form?.message} +

{form.message}

+ {/if} + `; + }, + }, + // Basic auth and/or magic link { name: ({ kit, typescript }) => `${kit.routesDirectory}/auth/confirm/+server.${typescript.installed ? 'ts' : 'js'}`, @@ -449,6 +573,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge `; }, }, + // OAuth only { name: ({ kit, typescript }) => `${kit.routesDirectory}/auth/callback/+server.${typescript.installed ? 'ts' : 'js'}`, @@ -478,6 +603,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge `; }, }, + // Admin client helper { name: ({ kit, typescript }) => `${kit.libDirectory}/server/supabase-admin.${typescript.installed ? 'ts' : 'js'}`, @@ -506,6 +632,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge `; }, }, + // Helper scripts { name: () => `package.json`, contentType: 'json', @@ -522,6 +649,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge } }, }, + // CLI local development configuration { name: () => './supabase/config.toml', contentType: 'text', @@ -539,6 +667,11 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge [auth.email.template.confirmation] subject = "Confirm Your Signup" content_path = "./supabase/templates/confirmation.html" + + # Custom password reset request template + [auth.email.template.recovery] + subject = "Reset Your Password" + content_path = "./supabase/templates/recovery.html" `, ); } @@ -594,7 +727,26 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge `; }, }, - // Demo routes when user has selected Basic Auth + { + name: () => './supabase/templates/recovery.html', + contentType: 'text', + condition: ({ options }) => options.cli && options.auth.includes('basic'), + content: () => { + return dedent` + + +

Reset Password

+

Follow this link to reset your password:

+

Reset Password

+ + + `; + }, + }, + // Demo routes when user has selected Basic Auth and/or Magic Link { name: ({ kit }) => `${kit.routesDirectory}/+page.svelte`, contentType: 'text', @@ -610,6 +762,9 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge
  • Login
  • Protected page
  • + {#if $page.data.user} + Logout + {/if}
     						User: {JSON.stringify($page.data.user, null, 2)}
     					
    @@ -653,7 +808,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge - + Logout
    From b08123c6642348a6e7e49e241bc8c3f6df9e4f2f Mon Sep 17 00:00:00 2001 From: Matthew Stibbard Date: Sat, 7 Sep 2024 14:18:14 +1000 Subject: [PATCH 23/76] add local google oauth. move oauth server-side --- adders/supabase/config/adder.ts | 133 +++++++++++++++++++++++--------- 1 file changed, 98 insertions(+), 35 deletions(-) diff --git a/adders/supabase/config/adder.ts b/adders/supabase/config/adder.ts index b0124c1b..9b06722e 100644 --- a/adders/supabase/config/adder.ts +++ b/adders/supabase/config/adder.ts @@ -262,6 +262,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge const isBasic = auth.includes('basic'); const isMagicLink = auth.includes('magicLink'); + const isOAuth = auth.includes('oauth'); return dedent` ${ @@ -271,7 +272,9 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge import { PUBLIC_BASE_URL } from '$env/static/public'` : '' } - ${isTs ? `import type { Actions } from './$types'\n` : ''} + ${isTs ? `import type { Actions } from './$types'` : ''} + ${isTs && isOAuth ? `import type { Provider } from '@supabase/supabase-js'` : ''} + export const actions${isTs ? `: Actions` : ''} = {${ isBasic ? ` @@ -312,9 +315,9 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge if (error) { console.error(error) return { message: 'Something went wrong, please try again.' } - } else { - redirect(303, '/private') } + + redirect(303, '${isDemo ? '/private' : '/'}') },` : '' }${ @@ -328,9 +331,32 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge if (error) { console.error(error) return { message: 'Something went wrong, please try again.' } - } else { - return { message: 'Check your email inbox.' } } + + return { message: 'Check your email inbox.' } + + },` + : '' + } + ${ + isOAuth + ? ` + oauth: async ({ request, locals: { supabase } }) => { + const formData = await request.formData() + const provider = formData.get('provider')${isTs ? ' as Provider' : ''} + + const { data, error } = await supabase.auth.signInWithOAuth({ + provider, + options: { + redirectTo: \`\${PUBLIC_BASE_URL}/auth/callback\`, + } + }) + if (error) { + console.error(error) + return { message: 'Something went wrong, please try again.' } + } + + redirect(303, data.url) },` : '' } @@ -360,26 +386,15 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge : '' } ${(isBasic || isMagicLink) && isTs ? `import type { ActionData } from './$types'` : ''} + ${isBasic || isMagicLink ? `export let form${isTs ? ': ActionData' : ''}` : ''} - ${ - isOAuth - ? ` - async function googleOAuth() { - await $page.data.supabase.auth.signInWithOAuth({ - provider: 'google', - options: { - redirectTo: \`\${PUBLIC_BASE_URL}/auth/callback\`, - }, - }) - }` - : '' - } + ${isOAuth ? `let provider = ''` : ''} +
    ${ isBasic || isMagicLink ? ` - + Forgot password? + ` : '' } ${isMagicLink ? '' : ''} - ${isBasic || isMagicLink ? `
    ` : ''} - ${isBasic ? `Forgot password?` : ''} - ${isOAuth ? `` : ''} ${ - isBasic || isMagicLink + isOAuth ? ` - {#if form?.message} -

    {form.message}

    - {/if}` + + + ` : '' } + + + + {#if form?.message} +

    {form.message}

    + {/if} `; }, }, @@ -655,10 +675,14 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge contentType: 'text', condition: ({ options }) => options.cli, content: ({ content, options }) => { + const isBasic = options.auth.includes('basic'); + const isMagicLink = options.auth.includes('magicLink'); + const isOAuth = options.auth.includes('oauth'); + content = content.replace('"http://127.0.0.1:3000"', '"http://localhost:5173"'); content = content.replace('"https://127.0.0.1:3000"', '"https://localhost:5173/*"'); - if (options.auth.includes('basic')) { + if (isBasic) { content = content.replace('enable_confirmations = false', 'enable_confirmations = true'); content = appendContent( content, @@ -675,7 +699,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge `, ); } - if (options.auth.includes('magicLink')) { + if (isMagicLink) { content = appendContent( content, dedent` @@ -686,6 +710,20 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge `, ); } + if (isOAuth) { + content = appendContent( + content, + dedent` + \n# Local Google auth configuration + [auth.external.google] + enabled = true + client_id = "env(SUPABASE_AUTH_EXTERNAL_GOOGLE_CLIENT_ID)" + secret = "env(SUPABASE_AUTH_EXTERNAL_GOOGLE_SECRET)" + redirect_uri = "http://127.0.0.1:54321/auth/v1/callback" + skip_nonce_check = true + `, + ); + } return content; }, @@ -755,6 +793,13 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge return dedent` import { page } from "$app/stores"; + + $: logout = async () => { + const { error } = await $page.data.supabase.auth.signOut(); + if (error) { + console.error(error); + } + };

    Welcome to SvelteKit with Supabase

    @@ -959,7 +1004,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge } if (isBasic || isMagicLink) { - steps.push(`Remember to update your hosted project's email templates`); + steps.push(`Update your hosted project's email templates`); if (isCli) { steps.push(`Local email templates are located in ${colors.green(`./supabase/templates`)}`); @@ -968,13 +1013,15 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge if (isOAuth) { steps.push(dedent` - OAuth added 'Login with Google' by default. Update ${colors.green(`src/routes/auth/+page.server.js/ts`)} with chosen provider(s)`); + ${colors.bold(`OAuth:`)} Refer to the docs for other OAuth providers: https://supabase.com/docs/guides/auth/social-login`); steps.push(dedent` - There are additional steps required for each OAuth provider: https://supabase.com/docs/guides/auth/social-login`); + ${colors.bold(`OAuth:`)} Enable Google in your hosted project dashboard and populate with your application's Google OAuth credentials. Create them via: https://console.cloud.google.com/apis/credentials/consent`); if (isCli) { steps.push(dedent` - Update ${colors.green(`./supabase/config.toml`)} with your chosen OAuth provider(s) details. Look for ${colors.blue(`[auth.external.apple]`)} as an example`); + ${colors.bold(`OAuth (Local Dev):`)} Add your application's Google OAuth credentials to ${colors.green(`.env`)}. Create them via: https://console.cloud.google.com/apis/credentials/consent`); + steps.push(dedent` + ${colors.bold(`OAuth (Local Dev):`)} To enable other local providers (or disable Google) update ${colors.green(`./supabase/config.toml`)} and restart the local Supabase services`); } } @@ -983,18 +1030,21 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge }); function generateEnvFileContent({ content, options }: TextFileEditorArgs) { + const isCli = options.cli; + const isOAuth = options.auth.includes('oauth'); + content = addEnvVar(content, 'PUBLIC_BASE_URL', '"http://localhost:5173"'); content = addEnvVar( content, 'PUBLIC_SUPABASE_URL', // Local development env always has the same credentials, prepopulate the local dev env file - options.cli ? '"http://127.0.0.1:54321"' : '""', + isCli ? '"http://127.0.0.1:54321"' : '""', ); content = addEnvVar( content, 'PUBLIC_SUPABASE_ANON_KEY', // Local development env always has the same credentials, prepopulate the local dev env file - options.cli + isCli ? '"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0"' : '""', ); @@ -1004,12 +1054,25 @@ function generateEnvFileContent({ content, options }: TextFileEditorArgs"', ) : content; + if (isOAuth) { + content = addEnvVar( + content, + 'SUPABASE_AUTH_EXTERNAL_GOOGLE_CLIENT_ID', + '" Date: Thu, 12 Sep 2024 10:11:48 +1000 Subject: [PATCH 24/76] add changeset --- .changeset/four-cheetahs-argue.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changeset/four-cheetahs-argue.md diff --git a/.changeset/four-cheetahs-argue.md b/.changeset/four-cheetahs-argue.md new file mode 100644 index 00000000..05d00735 --- /dev/null +++ b/.changeset/four-cheetahs-argue.md @@ -0,0 +1,10 @@ +--- +'@svelte-add/testing-library': minor +'@svelte-add/config': minor +'@svelte-add/core': minor +'@svelte-add/adders': minor +--- + +feat: auto detect package manager +feat: add support for executing scripts (e.g., npx, pnpm dlx, etc) within an adder +feat: add support for multiselect options within an adder From 1365d0f136f294433d9ea5f3626c98d1294b4d6f Mon Sep 17 00:00:00 2001 From: Matthew Stibbard Date: Thu, 12 Sep 2024 13:09:06 +1000 Subject: [PATCH 25/76] fix unused imports --- adders/supabase/config/adder.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/adders/supabase/config/adder.ts b/adders/supabase/config/adder.ts index 9b06722e..553a25f9 100644 --- a/adders/supabase/config/adder.ts +++ b/adders/supabase/config/adder.ts @@ -62,7 +62,9 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge return dedent` import { createServerClient } from '@supabase/ssr' - import {${isTs ? ' type Handle,' : ''} redirect } from '@sveltejs/kit' + ${!isTs && isDemo ? `import { redirect } from '@sveltejs/kit'` : ''} + ${isTs && isDemo ? `import { type Handle, redirect } from '@sveltejs/kit'` : ''} + ${isTs && !isDemo ? `import type { Handle } from '@sveltejs/kit'` : ''} import { sequence } from '@sveltejs/kit/hooks' import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public' @@ -265,13 +267,8 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge const isOAuth = auth.includes('oauth'); return dedent` - ${ - isBasic - ? ` - import { redirect } from '@sveltejs/kit' - import { PUBLIC_BASE_URL } from '$env/static/public'` - : '' - } + ${isBasic || isOAuth ? `import { redirect } from '@sveltejs/kit'` : ''} + ${isDemo || isOAuth ? `import { PUBLIC_BASE_URL } from '$env/static/public'` : ''} ${isTs ? `import type { Actions } from './$types'` : ''} ${isTs && isOAuth ? `import type { Provider } from '@supabase/supabase-js'` : ''} From 902383aca0cd9d43dc19f4776e098880478dd32d Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Fri, 27 Sep 2024 20:48:47 +0200 Subject: [PATCH 26/76] move supabase adder --- {adders => packages/adders}/supabase/config/adder.ts | 0 {adders => packages/adders}/supabase/config/checks.ts | 0 {adders => packages/adders}/supabase/config/options.ts | 0 {adders => packages/adders}/supabase/config/tests.ts | 0 {adders => packages/adders}/supabase/index.ts | 0 {adders => packages/adders}/supabase/supabase.svg | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename {adders => packages/adders}/supabase/config/adder.ts (100%) rename {adders => packages/adders}/supabase/config/checks.ts (100%) rename {adders => packages/adders}/supabase/config/options.ts (100%) rename {adders => packages/adders}/supabase/config/tests.ts (100%) rename {adders => packages/adders}/supabase/index.ts (100%) rename {adders => packages/adders}/supabase/supabase.svg (100%) diff --git a/adders/supabase/config/adder.ts b/packages/adders/supabase/config/adder.ts similarity index 100% rename from adders/supabase/config/adder.ts rename to packages/adders/supabase/config/adder.ts diff --git a/adders/supabase/config/checks.ts b/packages/adders/supabase/config/checks.ts similarity index 100% rename from adders/supabase/config/checks.ts rename to packages/adders/supabase/config/checks.ts diff --git a/adders/supabase/config/options.ts b/packages/adders/supabase/config/options.ts similarity index 100% rename from adders/supabase/config/options.ts rename to packages/adders/supabase/config/options.ts diff --git a/adders/supabase/config/tests.ts b/packages/adders/supabase/config/tests.ts similarity index 100% rename from adders/supabase/config/tests.ts rename to packages/adders/supabase/config/tests.ts diff --git a/adders/supabase/index.ts b/packages/adders/supabase/index.ts similarity index 100% rename from adders/supabase/index.ts rename to packages/adders/supabase/index.ts diff --git a/adders/supabase/supabase.svg b/packages/adders/supabase/supabase.svg similarity index 100% rename from adders/supabase/supabase.svg rename to packages/adders/supabase/supabase.svg From 9e098bd2d44254890a656014e05a829754daca45 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Fri, 27 Sep 2024 21:11:32 +0200 Subject: [PATCH 27/76] use `@svelte-cli/core` and formatting --- packages/adders/supabase/config/adder.ts | 92 +++++++++++----------- packages/adders/supabase/config/checks.ts | 4 +- packages/adders/supabase/config/options.ts | 22 +++--- packages/adders/supabase/config/tests.ts | 4 +- packages/adders/supabase/index.ts | 2 +- 5 files changed, 62 insertions(+), 62 deletions(-) diff --git a/packages/adders/supabase/config/adder.ts b/packages/adders/supabase/config/adder.ts index 553a25f9..589f67a2 100644 --- a/packages/adders/supabase/config/adder.ts +++ b/packages/adders/supabase/config/adder.ts @@ -1,5 +1,5 @@ -import { defineAdderConfig, dedent, type TextFileEditorArgs, colors } from '@svelte-add/core'; -import { options as availableOptions } from './options'; +import { defineAdderConfig, dedent, type TextFileEditorArgs, colors } from '@svelte-cli/core'; +import { options as availableOptions } from './options.ts'; export const adder = defineAdderConfig({ metadata: { @@ -11,8 +11,8 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge website: { logo: './supabase.svg', keywords: ['supabase', 'database', 'postgres', 'auth'], - documentation: 'https://supabase.com/docs', - }, + documentation: 'https://supabase.com/docs' + } }, options: availableOptions, integrationType: 'inline', @@ -22,34 +22,34 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge name: '@supabase/ssr', version: '^0.5.1', dev: false, - condition: ({ options }) => options.auth.length > 0, + condition: ({ options }) => options.auth.length > 0 }, // Local development CLI { name: 'supabase', version: '^1.191.3', dev: true, - condition: ({ options }) => options.cli, - }, + condition: ({ options }) => options.cli + } ], scripts: [ { description: 'Supabase CLI initialization', args: ['supabase', 'init', '--with-intellij-settings=false', '--with-vscode-settings=false'], type: 'dependency', - condition: ({ options }) => options.cli, - }, + condition: ({ options }) => options.cli + } ], files: [ { name: () => `.env`, contentType: 'text', - content: generateEnvFileContent, + content: generateEnvFileContent }, { name: () => `.env.example`, contentType: 'text', - content: generateEnvFileContent, + content: generateEnvFileContent }, // Common to all Auth options { @@ -131,7 +131,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge export const handle${isTs ? ': Handle' : ''} = sequence(supabase, authGuard) `; - }, + } }, { name: () => './src/app.d.ts', @@ -162,7 +162,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge export {} `; - }, + } }, { name: ({ kit, typescript }) => @@ -203,7 +203,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge return { session, supabase, user } } `; - }, + } }, { name: ({ kit, typescript }) => @@ -222,7 +222,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge } } `; - }, + } }, { name: ({ kit }) => `${kit.routesDirectory}/+layout.svelte`, @@ -250,7 +250,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge `; - }, + } }, { name: ({ kit, typescript }) => @@ -359,7 +359,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge } } `; - }, + } }, { name: ({ kit }) => `${kit.routesDirectory}/auth/+page.svelte`, @@ -427,7 +427,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge

    {form.message}

    {/if} `; - }, + } }, // Basic auth specific { @@ -458,7 +458,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge

    {form.message}

    {/if} `; - }, + } }, { name: ({ kit, typescript }) => @@ -490,7 +490,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge }, } `; - }, + } }, { name: ({ kit, typescript }) => @@ -518,7 +518,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge }, } `; - }, + } }, { name: ({ kit }) => `${kit.routesDirectory}/auth/reset-password/+page.svelte`, @@ -548,7 +548,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge

    {form.message}

    {/if} `; - }, + } }, // Basic auth and/or magic link { @@ -588,7 +588,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge redirect(303, redirectTo) } `; - }, + } }, // OAuth only { @@ -618,7 +618,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge throw redirect(303, \`/\${next.slice(1)}\`) } `; - }, + } }, // Admin client helper { @@ -647,7 +647,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge }, ); `; - }, + } }, // Helper scripts { @@ -664,7 +664,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge scripts['db:types'] ??= 'supabase gen types typescript --local > src/lib/supabase-types.ts'; } - }, + } }, // CLI local development configuration { @@ -693,7 +693,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge [auth.email.template.recovery] subject = "Reset Your Password" content_path = "./supabase/templates/recovery.html" - `, + ` ); } if (isMagicLink) { @@ -704,7 +704,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge [auth.email.template.magic_link] subject = "Your Magic Link" content_path = "./supabase/templates/magic_link.html" - `, + ` ); } if (isOAuth) { @@ -718,12 +718,12 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge secret = "env(SUPABASE_AUTH_EXTERNAL_GOOGLE_SECRET)" redirect_uri = "http://127.0.0.1:54321/auth/v1/callback" skip_nonce_check = true - `, + ` ); } return content; - }, + } }, { name: () => './supabase/templates/confirmation.html', @@ -742,7 +742,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge `; - }, + } }, { name: () => './supabase/templates/magic_link.html', @@ -760,7 +760,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge `; - }, + } }, { name: () => './supabase/templates/recovery.html', @@ -779,7 +779,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge `; - }, + } }, // Demo routes when user has selected Basic Auth and/or Magic Link { @@ -811,7 +811,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge User: {JSON.stringify($page.data.user, null, 2)} `; - }, + } }, { name: ({ kit, typescript }) => @@ -826,7 +826,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge * send a server request, and thus trigger \`hooks.server.ts\`. **/ `; - }, + } }, { name: ({ kit }) => `${kit.routesDirectory}/private/+layout.svelte`, @@ -856,7 +856,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge
    `; - }, + } }, { name: ({ kit }) => `${kit.routesDirectory}/private/+page.svelte`, @@ -918,7 +918,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge : '' } `; - }, + } }, { name: ({ kit, typescript }) => @@ -936,7 +936,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge return { notes: notes ?? [] } } `; - }, + } }, { name: () => './supabase/migrations/00000000000000_demo.sql', @@ -966,8 +966,8 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge to authenticated using ((select auth.uid()) = user_id); `; - }, - }, + } + } ], nextSteps: ({ options, packageManager }) => { let command: string; @@ -1023,7 +1023,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge } return steps; - }, + } }); function generateEnvFileContent({ content, options }: TextFileEditorArgs) { @@ -1035,7 +1035,7 @@ function generateEnvFileContent({ content, options }: TextFileEditorArgs"', + isCli ? '"http://127.0.0.1:54321"' : '""' ); content = addEnvVar( content, @@ -1043,7 +1043,7 @@ function generateEnvFileContent({ content, options }: TextFileEditorArgs"', + : '""' ); content = options.admin @@ -1053,7 +1053,7 @@ function generateEnvFileContent({ content, options }: TextFileEditorArgs"', + : '""' ) : content; @@ -1061,12 +1061,12 @@ function generateEnvFileContent({ content, options }: TextFileEditorArgs cli === true, + condition: ({ cli }) => cli === true }, demo: { question: 'Do you want to include demo routes to show protected routes?', type: 'boolean', default: false, condition: ({ auth }) => - (auth as unknown[]).includes('basic') || (auth as unknown[]).includes('magicLink'), - }, + (auth as unknown[]).includes('basic') || (auth as unknown[]).includes('magicLink') + } }); diff --git a/packages/adders/supabase/config/tests.ts b/packages/adders/supabase/config/tests.ts index 524e74cc..80ec6e0e 100644 --- a/packages/adders/supabase/config/tests.ts +++ b/packages/adders/supabase/config/tests.ts @@ -1,9 +1,9 @@ -import { defineAdderTests } from '@svelte-add/core'; +import { defineAdderTests } from '@svelte-cli/core'; import { options } from './options.js'; export const tests = defineAdderTests({ files: [], options, optionValues: [], - tests: [], + tests: [] }); diff --git a/packages/adders/supabase/index.ts b/packages/adders/supabase/index.ts index 08f7ba75..24861cbd 100644 --- a/packages/adders/supabase/index.ts +++ b/packages/adders/supabase/index.ts @@ -1,6 +1,6 @@ #!/usr/bin/env node -import { defineAdder } from '@svelte-add/core'; +import { defineAdder } from '@svelte-cli/core'; import { adder } from './config/adder.js'; import { tests } from './config/tests.js'; import { checks } from './config/checks.js'; From f66e3485bf52b862882fa38f3556c7a5e14977f4 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sat, 28 Sep 2024 15:01:18 +0200 Subject: [PATCH 28/76] fix most important merge conflicts --- packages/adders/_config/categories.ts | 8 - packages/core/adder/config.ts | 17 -- packages/core/adder/execute.ts.rej | 17 -- packages/core/adder/options.ts | 281 -------------------------- 4 files changed, 323 deletions(-) delete mode 100644 packages/core/adder/execute.ts.rej diff --git a/packages/adders/_config/categories.ts b/packages/adders/_config/categories.ts index 7f94ee20..7e2bb8a7 100644 --- a/packages/adders/_config/categories.ts +++ b/packages/adders/_config/categories.ts @@ -6,14 +6,6 @@ export type CategoryInfo = { description: string; }; -<<<<<<< HEAD:packages/adders/_config/categories.ts -<<<<<<< HEAD:packages/adders/_config/categories.ts -======= -export type CategoryKeys = 'codeQuality' | 'css' | 'db' | 'testing' | 'additional' | 'baas'; ->>>>>>> a19c799 (add baas category):packages/config/categories.ts -======= -export type CategoryKeys = 'codeQuality' | 'css' | 'db' | 'testing' | 'additional'; ->>>>>>> f47d2f4 (Apply suggestions from benmccann and manuel3108):packages/config/categories.ts export type CategoryDetails = Record; export type AdderCategories = Record; diff --git a/packages/core/adder/config.ts b/packages/core/adder/config.ts index 7e5d30d7..4972aa5a 100644 --- a/packages/core/adder/config.ts +++ b/packages/core/adder/config.ts @@ -1,24 +1,7 @@ -<<<<<<< HEAD import type { OptionDefinition, OptionValues, Question } from './options.ts'; import type { FileType } from '../files/processors.ts'; import type { Workspace } from '../files/workspace.ts'; import type { Colors } from 'picocolors/types.ts'; -======= -import type { - CssAstEditor, - HtmlAstEditor, - JsAstEditor, - SvelteAstEditor -} from '@svelte-cli/ast-manipulation'; -import type { OptionDefinition, OptionValues, Question } from './options.js'; -import type { FileTypes } from '../files/processors.js'; -import type { Workspace } from '../utils/workspace.js'; -import type { Postcondition } from './postconditions.js'; -import type { Colors } from 'picocolors/types.js'; -import type { PackageManager } from '../utils/dependencies.js'; - -export type { CssAstEditor, HtmlAstEditor, JsAstEditor, SvelteAstEditor }; ->>>>>>> e647284 (add packageManager to nextSteps data object) export type ConditionDefinition = ( Workspace: Workspace diff --git a/packages/core/adder/execute.ts.rej b/packages/core/adder/execute.ts.rej deleted file mode 100644 index d1223d5f..00000000 --- a/packages/core/adder/execute.ts.rej +++ /dev/null @@ -1,17 +0,0 @@ -diff a/packages/core/adder/execute.ts b/packages/core/adder/execute.ts (rejected hunks) -@@ -31,7 +31,6 @@ import type { - import type { RemoteControlOptions } from './remoteControl.js'; - import { - getPackageManager, -- installDependencies, - suggestInstallingDependencies, - type PackageManager, - } from '../utils/dependencies.js'; -@@ -41,6 +40,7 @@ import { checkPostconditions, printUnmetPostconditions } from './postconditions. - import { displayNextSteps } from './nextSteps.js'; - import { spinner, log, cancel } from '@svelte-add/clack-prompts'; - import { executeCli } from '../utils/cli.js'; -+import { COMMANDS } from 'package-manager-detector/agents'; - - export type AdderDetails = { - config: AdderConfig; diff --git a/packages/core/adder/options.ts b/packages/core/adder/options.ts index 0994c4e8..73a469c7 100644 --- a/packages/core/adder/options.ts +++ b/packages/core/adder/options.ts @@ -1,16 +1,3 @@ -<<<<<<< HEAD -======= -import { type OptionValues as CliOptionValues, program } from 'commander'; -import { - booleanPrompt, - multiSelectPrompt, - selectPrompt, - textPrompt, - type PromptOption, -} from '../utils/prompts.js'; -import type { AdderDetails, AddersExecutionPlan } from './execute.js'; - ->>>>>>> e9ccf41 (multiselect auth option: basic, magic link, oauth. better nextSteps) export type BooleanQuestion = { type: 'boolean'; default: boolean; @@ -63,274 +50,6 @@ export type OptionValues = { : Args[K] extends SelectQuestion ? Value : Args[K] extends MultiSelectQuestion -<<<<<<< HEAD ? Value[] : never; }; -======= - ? Value - : never; -}; - -export type AvailableCliOptionKeys = keyof AvailableCliOptionKeyTypes; -export type AvailableCliOptionKeyTypes = { - default: boolean; - path: string; - skipPreconditions: boolean; - skipInstall: boolean; -}; - -export type AvailableCliOptionValues = { - [K in AvailableCliOptionKeys]?: AvailableCliOptionKeyTypes[K]; -} & { adders?: string[] }; - -export type AvailableCliOption = { - cliArg: string; - processedCliArg: string; // `commander` will transform the cli name if the arg names contains `-` - description: string; - allowShorthand: boolean; -} & (BooleanQuestion | StringQuestion); -export type AvailableCliOptions = Record; - -export const availableCliOptions: AvailableCliOptions = { - default: { - cliArg: 'default', - processedCliArg: 'default', - type: 'boolean', - default: false, - description: 'Installs default adder options for unspecified options', - allowShorthand: true - }, - path: { - cliArg: 'path', - processedCliArg: 'path', - type: 'string', - default: './', - description: 'Path to working directory', - allowShorthand: false - }, - skipPreconditions: { - cliArg: 'skip-preconditions', - processedCliArg: 'skipPreconditions', - type: 'boolean', - default: false, - description: 'Skips validating preconditions before running the adder', - allowShorthand: true - }, - skipInstall: { - cliArg: 'skip-install', - processedCliArg: 'skipInstall', - type: 'boolean', - default: false, - description: 'Skips installing dependencies after applying the adder', - allowShorthand: true - } -}; - -export function prepareAndParseCliOptions( - adderDetails: Array> -) { - const multipleAdders = adderDetails.length > 1; - - for (const option of Object.values(availableCliOptions)) { - if (option.allowShorthand) { - program.option(`--${option.cliArg} [${option.type}]`, option.description); - } else { - program.option(`--${option.cliArg} <${option.type}>`, option.description); - } - } - - if (multipleAdders) { - program.argument('[adders...]', 'List of adders to install'); - } - - const addersWithOptions = adderDetails.filter((x) => Object.keys(x.config.options).length > 0); - - for (const { config } of addersWithOptions) { - for (const optionKey of Object.keys(config.options)) { - const option = config.options[optionKey]; - - let optionString; - if (multipleAdders) { - optionString = `--${config.metadata.id}-${optionKey} [${option.type}]`; - } else { - optionString = `--${optionKey} [${option.type}]`; - } - - program.option(optionString, option.question); - } - } - - program.parse(); - const options = program.opts(); - - if (multipleAdders) { - let selectedAdderIds = program.args ?? []; - - // replace aliases with adder ids - selectedAdderIds = selectedAdderIds.map((id) => { - const adder = adderDetails.find(({ config }) => config.metadata?.alias === id); - return adder ? adder.config.metadata.id : id; - }); - - validateAdders(adderDetails, selectedAdderIds); - - options.adder = selectedAdderIds; - } - - return options; -} - -function validateAdders( - adderDetails: Array>, - selectedAdderIds: string[] -) { - const validAdderIds = adderDetails.map((x) => x.config.metadata.id); - const invalidAdders = selectedAdderIds.filter((x) => !validAdderIds.includes(x)); - - if (invalidAdders.length > 0) { - console.error( - `Invalid adder${invalidAdders.length > 1 ? 's' : ''} selected:`, - invalidAdders.join(', ') - ); - process.exit(1); - } -} - -export function ensureCorrectOptionTypes( - adderDetails: Array>, - cliOptionsByAdderId: Record> -) { - let foundInvalidType = false; - - for (const { config } of adderDetails) { - const adderId = config.metadata.id; - - for (const optionKey of Object.keys(config.options)) { - const option = config.options[optionKey]; - const value = cliOptionsByAdderId[adderId][optionKey]; - - if (value == undefined) { - continue; - } else if (option.type == 'boolean' && typeof value == 'boolean') { - continue; - } else if (option.type == 'number' && typeof value == 'number') { - continue; - } else if ( - option.type == 'number' && - typeof value == 'string' && - typeof parseInt(value) == 'number' && - !isNaN(parseInt(value)) - ) { - cliOptionsByAdderId[adderId][optionKey] = parseInt(value); - continue; - } else if (option.type == 'string' && typeof value == 'string') { - continue; - } else if (option.type === 'select') { - continue; - } else if (option.type === 'multiselect') { - continue; - } - - foundInvalidType = true; - console.log( - `Option ${optionKey} needs to be of type ${option.type} but was of type ${typeof value}!` - ); - } - } - - if (foundInvalidType) { - console.log('Found invalid option type. Exiting.'); - process.exit(0); - } -} - -export function extractCommonCliOptions(cliOptions: CliOptionValues) { - const typedOption = (name: string) => cliOptions[name] as T; - - const commonOptions: AvailableCliOptionValues = { - default: typedOption(availableCliOptions.default.processedCliArg), - path: typedOption(availableCliOptions.path.processedCliArg), - skipInstall: typedOption(availableCliOptions.skipInstall.processedCliArg), - skipPreconditions: typedOption(availableCliOptions.skipPreconditions.processedCliArg), - adders: typedOption('adder') - }; - - return commonOptions; -} - -export function extractAdderCliOptions( - cliOptions: CliOptionValues, - adderDetails: Array> -) { - const multipleAdders = adderDetails.length > 1; - - const options: Record> = {}; - for (const { config } of adderDetails) { - const adderId = config.metadata.id; - options[adderId] = {}; - - for (const optionKey of Object.keys(config.options)) { - let cliOptionKey = optionKey; - - if (multipleAdders) cliOptionKey = `${adderId}${upperCaseFirstLetter(cliOptionKey)}`; - - let optionValue = cliOptions[cliOptionKey] as unknown; - if (optionValue === 'true') optionValue = true; - else if (optionValue === 'false') optionValue = false; - - options[adderId][optionKey] = optionValue; - } - } - - return options; -} - -function upperCaseFirstLetter(string: string) { - return string.charAt(0).toLocaleUpperCase() + string.slice(1); -} - -export async function requestMissingOptionsFromUser( - adderDetails: Array>, - executionPlan: AddersExecutionPlan -) { - for (const { config } of adderDetails) { - const adderId = config.metadata.id; - const questionPrefix = adderDetails.length > 1 ? `${config.metadata.name}: ` : ''; - - for (const optionKey of Object.keys(config.options)) { - const option = config.options[optionKey]; - const selectedValues = executionPlan.cliOptionsByAdderId[adderId]; - const skipQuestion = option.condition?.(selectedValues) === false; - - if (!selectedValues || skipQuestion) continue; - - let optionValue = selectedValues[optionKey]; - - // if the option already has an value, ignore it and continue - if (optionValue !== undefined) continue; - - if (option.type == 'number' || option.type == 'string') { - optionValue = await textPrompt( - questionPrefix + option.question, - 'Not sure', - option.default.toString() - ); - } else if (option.type == 'boolean') { - optionValue = await booleanPrompt(questionPrefix + option.question, option.default); - } else if (option.type == 'select') { - optionValue = await selectPrompt( - questionPrefix + option.question, - option.default, - option.options - ); - } - - if (optionValue === 'true') optionValue = true; - if (optionValue === 'false') optionValue = false; - - selectedValues[optionKey] = optionValue; - } - } -} ->>>>>>> e9ccf41 (multiselect auth option: basic, magic link, oauth. better nextSteps) From f041ba874d420b96dc5e64f7d4e14f1d99bc4fd1 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sat, 28 Sep 2024 15:38:55 +0200 Subject: [PATCH 29/76] update adder --- packages/adders/supabase/config/adder.ts | 121 +++++++++++----------- packages/adders/supabase/config/checks.ts | 2 +- packages/adders/supabase/config/tests.ts | 2 +- 3 files changed, 62 insertions(+), 63 deletions(-) diff --git a/packages/adders/supabase/config/adder.ts b/packages/adders/supabase/config/adder.ts index 589f67a2..2cd43e9f 100644 --- a/packages/adders/supabase/config/adder.ts +++ b/packages/adders/supabase/config/adder.ts @@ -1,4 +1,4 @@ -import { defineAdderConfig, dedent, type TextFileEditorArgs, colors } from '@svelte-cli/core'; +import { defineAdderConfig, dedent, type TextFileEditor, colors } from '@svelte-cli/core'; import { options as availableOptions } from './options.ts'; export const adder = defineAdderConfig({ @@ -32,32 +32,32 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge condition: ({ options }) => options.cli } ], - scripts: [ - { - description: 'Supabase CLI initialization', - args: ['supabase', 'init', '--with-intellij-settings=false', '--with-vscode-settings=false'], - type: 'dependency', - condition: ({ options }) => options.cli - } - ], + // scripts: [ + // { + // description: 'Supabase CLI initialization', + // args: ['supabase', 'init', '--with-intellij-settings=false', '--with-vscode-settings=false'], + // type: 'dependency', + // condition: ({ options }) => options.cli + // } + // ], files: [ { - name: () => `.env`, + name: () => '.env', contentType: 'text', content: generateEnvFileContent }, { - name: () => `.env.example`, + name: () => '.env.example', contentType: 'text', content: generateEnvFileContent }, // Common to all Auth options { - name: ({ typescript }) => `./src/hooks.server.${typescript.installed ? 'ts' : 'js'}`, + name: ({ typescript }) => `./src/hooks.server.${typescript ? 'ts' : 'js'}`, contentType: 'text', condition: ({ options }) => options.auth.length > 0, content: ({ options, typescript }) => { - const isTs = typescript.installed; + const isTs = typescript; const { demo: isDemo } = options; return dedent` @@ -136,7 +136,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge { name: () => './src/app.d.ts', contentType: 'text', - condition: ({ options, typescript }) => typescript.installed && options.auth.length > 0, + condition: ({ options, typescript }) => typescript && options.auth.length > 0, content: ({ options }) => { const { cli: isCli, helpers: isHelpers } = options; @@ -147,7 +147,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge namespace App { // interface Error {} interface Locals { - supabase: SupabaseClient${isCli && isHelpers ? `` : ''} + supabase: SupabaseClient${isCli && isHelpers ? '' : ''} safeGetSession: () => Promise<{ session: Session | null; user: User | null }> session: Session | null user: User | null @@ -165,12 +165,11 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge } }, { - name: ({ kit, typescript }) => - `${kit.routesDirectory}/+layout.${typescript.installed ? 'ts' : 'js'}`, + name: ({ kit, typescript }) => `${kit?.routesDirectory}/+layout.${typescript ? 'ts' : 'js'}`, contentType: 'text', condition: ({ options }) => options.auth.length > 0, content: ({ typescript }) => { - const isTs = typescript.installed; + const isTs = typescript; return dedent` import { createBrowserClient, createServerClient, isBrowser } from '@supabase/ssr' @@ -207,11 +206,11 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge }, { name: ({ kit, typescript }) => - `${kit.routesDirectory}/+layout.server.${typescript.installed ? 'ts' : 'js'}`, + `${kit?.routesDirectory}/+layout.server.${typescript ? 'ts' : 'js'}`, contentType: 'text', condition: ({ options }) => options.auth.length > 0, content: ({ typescript }) => { - const isTs = typescript.installed; + const isTs = typescript; return dedent` ${isTs ? `import type { LayoutServerLoad } from './$types'\n` : ''} @@ -225,12 +224,12 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge } }, { - name: ({ kit }) => `${kit.routesDirectory}/+layout.svelte`, + name: ({ kit }) => `${kit}/+layout.svelte`, contentType: 'text', condition: ({ options }) => options.auth.length > 0, content: ({ typescript }) => { return dedent` - + import { invalidate } from '$app/navigation'; import { onMount } from 'svelte'; @@ -254,12 +253,12 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge }, { name: ({ kit, typescript }) => - `${kit.routesDirectory}/auth/+page.server.${typescript.installed ? 'ts' : 'js'}`, + `${kit?.routesDirectory}/auth/+page.server.${typescript ? 'ts' : 'js'}`, contentType: 'text', condition: ({ options }) => options.auth.includes('basic') || options.auth.includes('magicLink'), content: ({ options, typescript }) => { - const isTs = typescript.installed; + const isTs = typescript; const { auth, demo: isDemo } = options; const isBasic = auth.includes('basic'); @@ -272,7 +271,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge ${isTs ? `import type { Actions } from './$types'` : ''} ${isTs && isOAuth ? `import type { Provider } from '@supabase/supabase-js'` : ''} - export const actions${isTs ? `: Actions` : ''} = {${ + export const actions${isTs ? ': Actions' : ''} = {${ isBasic ? ` signup: async ({ request, locals: { supabase } }) => { @@ -362,11 +361,11 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge } }, { - name: ({ kit }) => `${kit.routesDirectory}/auth/+page.svelte`, + name: ({ kit }) => `${kit}/auth/+page.svelte`, contentType: 'text', condition: ({ options }) => options.auth.length > 0, content: ({ options, typescript }) => { - const isTs = typescript.installed; + const isTs = typescript; const { auth } = options; const isBasic = auth.includes('basic'); @@ -431,11 +430,11 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge }, // Basic auth specific { - name: ({ kit }) => `${kit.routesDirectory}/auth/forgot-password/+page.svelte`, + name: ({ kit }) => `${kit?.routesDirectory}/auth/forgot-password/+page.svelte`, contentType: 'text', condition: ({ options }) => options.auth.includes('basic'), content: ({ typescript }) => { - const isTs = typescript.installed; + const isTs = typescript; return dedent` @@ -462,17 +461,17 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge }, { name: ({ kit, typescript }) => - `${kit.routesDirectory}/auth/forgot-password/+page.server.${typescript.installed ? 'ts' : 'js'}`, + `${kit?.routesDirectory}/auth/forgot-password/+page.server.${typescript ? 'ts' : 'js'}`, contentType: 'text', condition: ({ options }) => options.auth.includes('basic'), content: ({ typescript }) => { - const isTs = typescript.installed; + const isTs = typescript; return dedent` import { PUBLIC_BASE_URL } from '$env/static/public' ${isTs ? `import type { Actions } from './$types'` : ''} - export const actions${isTs ? `: Actions` : ''} = { + export const actions${isTs ? ': Actions' : ''} = { default: async ({ request, locals: { supabase } }) => { const formData = await request.formData() const email = formData.get('email')${isTs ? ' as string' : ''} @@ -494,16 +493,16 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge }, { name: ({ kit, typescript }) => - `${kit.routesDirectory}/auth/reset-password/+page.server.${typescript.installed ? 'ts' : 'js'}`, + `${kit?.routesDirectory}/auth/reset-password/+page.server.${typescript ? 'ts' : 'js'}`, contentType: 'text', condition: ({ options }) => options.auth.includes('basic'), content: ({ typescript }) => { - const isTs = typescript.installed; + const isTs = typescript; return dedent` ${isTs ? `import type { Actions } from './$types'` : ''} - export const actions${isTs ? `: Actions` : ''} = { + export const actions${isTs ? ': Actions' : ''} = { default: async ({ request, locals: { supabase } }) => { const formData = await request.formData() const password = formData.get('password')${isTs ? ' as string' : ''} @@ -521,11 +520,11 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge } }, { - name: ({ kit }) => `${kit.routesDirectory}/auth/reset-password/+page.svelte`, + name: ({ kit }) => `${kit?.routesDirectory}/auth/reset-password/+page.svelte`, contentType: 'text', condition: ({ options }) => options.auth.includes('basic'), content: ({ typescript }) => { - const isTs = typescript.installed; + const isTs = typescript; return dedent` @@ -553,12 +552,12 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge // Basic auth and/or magic link { name: ({ kit, typescript }) => - `${kit.routesDirectory}/auth/confirm/+server.${typescript.installed ? 'ts' : 'js'}`, + `${kit?.routesDirectory}/auth/confirm/+server.${typescript ? 'ts' : 'js'}`, contentType: 'text', condition: ({ options }) => options.auth.includes('basic') || options.auth.includes('magicLink'), content: ({ typescript }) => { - const isTs = typescript.installed; + const isTs = typescript; return dedent` import { error, redirect } from '@sveltejs/kit' @@ -593,11 +592,11 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge // OAuth only { name: ({ kit, typescript }) => - `${kit.routesDirectory}/auth/callback/+server.${typescript.installed ? 'ts' : 'js'}`, + `${kit?.routesDirectory}/auth/callback/+server.${typescript ? 'ts' : 'js'}`, contentType: 'text', condition: ({ options }) => options.auth.includes('oauth'), content: ({ typescript }) => { - const isTs = typescript.installed; + const isTs = typescript; return dedent` import { error, redirect } from '@sveltejs/kit' @@ -623,11 +622,11 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge // Admin client helper { name: ({ kit, typescript }) => - `${kit.libDirectory}/server/supabase-admin.${typescript.installed ? 'ts' : 'js'}`, + `${kit?.libDirectory}/server/supabase-admin.${typescript ? 'ts' : 'js'}`, contentType: 'text', condition: ({ options }) => options.admin, content: ({ options, typescript }) => { - const isTs = typescript.installed; + const isTs = typescript; const { cli: isCli, helpers: isHelpers } = options; return dedent` @@ -651,7 +650,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge }, // Helper scripts { - name: () => `package.json`, + name: () => 'package.json', contentType: 'json', condition: ({ options }) => options.helpers, content: ({ data, typescript }) => { @@ -660,7 +659,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge scripts['db:migration'] ??= 'supabase migration new'; scripts['db:migration:up'] ??= 'supabase migration up --local'; scripts['db:reset'] ??= 'supabase db reset'; - if (typescript.installed) { + if (typescript) { scripts['db:types'] ??= 'supabase gen types typescript --local > src/lib/supabase-types.ts'; } @@ -783,12 +782,12 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge }, // Demo routes when user has selected Basic Auth and/or Magic Link { - name: ({ kit }) => `${kit.routesDirectory}/+page.svelte`, + name: ({ kit }) => `${kit?.routesDirectory}/+page.svelte`, contentType: 'text', condition: ({ options }) => options.demo, content: ({ typescript }) => { return dedent` - + import { page } from "$app/stores"; $: logout = async () => { @@ -815,7 +814,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge }, { name: ({ kit, typescript }) => - `${kit.routesDirectory}/private/+layout.server.${typescript.installed ? 'ts' : 'js'}`, + `${kit?.routesDirectory}/private/+layout.server.${typescript ? 'ts' : 'js'}`, contentType: 'text', condition: ({ options }) => options.demo, content: () => { @@ -829,7 +828,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge } }, { - name: ({ kit }) => `${kit.routesDirectory}/private/+layout.svelte`, + name: ({ kit }) => `${kit?.routesDirectory}/private/+layout.svelte`, contentType: 'text', condition: ({ options }) => options.demo, content: () => { @@ -859,11 +858,11 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge } }, { - name: ({ kit }) => `${kit.routesDirectory}/private/+page.svelte`, + name: ({ kit }) => `${kit?.routesDirectory}/private/+page.svelte`, contentType: 'text', condition: ({ options }) => options.demo, content: ({ options, typescript }) => { - const isTs = typescript.installed; + const isTs = typescript; const { cli: isCli } = options; return dedent` @@ -922,11 +921,11 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge }, { name: ({ kit, typescript }) => - `${kit.routesDirectory}/private/+page.server.${typescript.installed ? 'ts' : 'js'}`, + `${kit?.routesDirectory}/private/+page.server.${typescript ? 'ts' : 'js'}`, contentType: 'text', condition: ({ options }) => options.demo && options.cli, content: ({ typescript }) => { - const isTs = typescript.installed; + const isTs = typescript; return dedent` ${isTs ? `import type { PageServerLoad } from './$types'\n` : ''} @@ -992,33 +991,33 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge if (isHelpers) { steps.push(dedent` - Check out ${colors.green(`package.json`)} for the helper scripts. Remember to generate your database types`); + Check out ${colors.green('package.json')} for the helper scripts. Remember to generate your database types`); } if (isBasic || isMagicLink || isOAuth) { steps.push(dedent` - Update authGuard in ${colors.green(`./src/hooks.server.js/ts`)} with your protected routes`); + Update authGuard in ${colors.green('./src/hooks.server.js/ts')} with your protected routes`); } if (isBasic || isMagicLink) { steps.push(`Update your hosted project's email templates`); if (isCli) { - steps.push(`Local email templates are located in ${colors.green(`./supabase/templates`)}`); + steps.push(`Local email templates are located in ${colors.green('./supabase/templates')}`); } } if (isOAuth) { steps.push(dedent` - ${colors.bold(`OAuth:`)} Refer to the docs for other OAuth providers: https://supabase.com/docs/guides/auth/social-login`); + ${colors.bold('OAuth:')} Refer to the docs for other OAuth providers: https://supabase.com/docs/guides/auth/social-login`); steps.push(dedent` - ${colors.bold(`OAuth:`)} Enable Google in your hosted project dashboard and populate with your application's Google OAuth credentials. Create them via: https://console.cloud.google.com/apis/credentials/consent`); + ${colors.bold('OAuth:')} Enable Google in your hosted project dashboard and populate with your application's Google OAuth credentials. Create them via: https://console.cloud.google.com/apis/credentials/consent`); if (isCli) { steps.push(dedent` - ${colors.bold(`OAuth (Local Dev):`)} Add your application's Google OAuth credentials to ${colors.green(`.env`)}. Create them via: https://console.cloud.google.com/apis/credentials/consent`); + ${colors.bold('OAuth (Local Dev):')} Add your application's Google OAuth credentials to ${colors.green('.env')}. Create them via: https://console.cloud.google.com/apis/credentials/consent`); steps.push(dedent` - ${colors.bold(`OAuth (Local Dev):`)} To enable other local providers (or disable Google) update ${colors.green(`./supabase/config.toml`)} and restart the local Supabase services`); + ${colors.bold('OAuth (Local Dev):')} To enable other local providers (or disable Google) update ${colors.green('./supabase/config.toml')} and restart the local Supabase services`); } } @@ -1026,7 +1025,7 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge } }); -function generateEnvFileContent({ content, options }: TextFileEditorArgs) { +function generateEnvFileContent({ content, options }: TextFileEditor) { const isCli = options.cli; const isOAuth = options.auth.includes('oauth'); diff --git a/packages/adders/supabase/config/checks.ts b/packages/adders/supabase/config/checks.ts index 88a4f60e..923c1632 100644 --- a/packages/adders/supabase/config/checks.ts +++ b/packages/adders/supabase/config/checks.ts @@ -1,5 +1,5 @@ import { defineAdderChecks } from '@svelte-cli/core'; -import { options } from './options'; +import { options } from './options.ts'; export const checks = defineAdderChecks({ options diff --git a/packages/adders/supabase/config/tests.ts b/packages/adders/supabase/config/tests.ts index 80ec6e0e..7a9f56ae 100644 --- a/packages/adders/supabase/config/tests.ts +++ b/packages/adders/supabase/config/tests.ts @@ -1,5 +1,5 @@ import { defineAdderTests } from '@svelte-cli/core'; -import { options } from './options.js'; +import { options } from './options.ts'; export const tests = defineAdderTests({ files: [], From 5cd1bff80e91a80e89b4640023dd29125654b734 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sat, 28 Sep 2024 18:30:19 +0200 Subject: [PATCH 30/76] re-add scripts in correct place --- .../adders/_config/adders/official.ts.rej | 12 ----- packages/adders/_config/categories.ts.rej | 11 ----- packages/adders/_config/official.ts | 3 +- packages/adders/supabase/config/adder.ts | 19 ++++--- packages/cli/commands/add.ts | 49 ++++++++++++++++--- packages/cli/commands/create.ts | 3 +- packages/cli/common.ts | 20 +------- packages/core/adder/config.ts | 5 +- packages/core/adder/config.ts.rej | 16 ------ packages/core/adder/nextSteps.ts.rej | 9 ---- packages/core/adder/options.ts.rej | 25 ---------- packages/core/files/processors.ts.rej | 15 ------ packages/core/files/workspace.ts | 26 +++++++++- packages/core/internal.ts | 7 ++- packages/core/package.json | 1 + packages/core/utils/create-project.ts.rej | 8 --- packages/core/utils/dependencies.ts.rej | 27 ---------- packages/core/utils/workspace.ts.rej | 28 ----------- pnpm-lock.yaml | 3 ++ 19 files changed, 94 insertions(+), 193 deletions(-) delete mode 100644 packages/adders/_config/adders/official.ts.rej delete mode 100644 packages/adders/_config/categories.ts.rej delete mode 100644 packages/core/adder/config.ts.rej delete mode 100644 packages/core/adder/nextSteps.ts.rej delete mode 100644 packages/core/adder/options.ts.rej delete mode 100644 packages/core/files/processors.ts.rej delete mode 100644 packages/core/utils/create-project.ts.rej delete mode 100644 packages/core/utils/dependencies.ts.rej delete mode 100644 packages/core/utils/workspace.ts.rej diff --git a/packages/adders/_config/adders/official.ts.rej b/packages/adders/_config/adders/official.ts.rej deleted file mode 100644 index 863f0be4..00000000 --- a/packages/adders/_config/adders/official.ts.rej +++ /dev/null @@ -1,12 +0,0 @@ -diff a/packages/config/adders/official.ts b/packages/config/adders/official.ts (rejected hunks) -@@ -4,9 +4,8 @@ export const adderCategories: AdderCategories = { - codeQuality: ['prettier', 'eslint'], - testing: ['vitest', 'playwright'], - css: ['tailwindcss'], -- db: ['drizzle'], -+ db: ['drizzle', 'supabase'], - additional: ['storybook', 'mdsvex', 'routify'], -- baas: ['supabase'], - }; - - export const adderIds = Object.values(adderCategories).flatMap((x) => x); diff --git a/packages/adders/_config/categories.ts.rej b/packages/adders/_config/categories.ts.rej deleted file mode 100644 index 50918f9b..00000000 --- a/packages/adders/_config/categories.ts.rej +++ /dev/null @@ -1,11 +0,0 @@ -diff a/packages/config/categories.ts b/packages/config/categories.ts (rejected hunks) -@@ -35,9 +35,4 @@ export const categories: CategoryDetails = { - name: 'Database', - description: '', - }, -- baas: { -- id: 'baas', -- name: 'Backend as a Service', -- description: 'Services that provide database, Auth, storage, etc.', -- }, - }; diff --git a/packages/adders/_config/official.ts b/packages/adders/_config/official.ts index f87eae7a..14ca6017 100644 --- a/packages/adders/_config/official.ts +++ b/packages/adders/_config/official.ts @@ -9,6 +9,7 @@ import playwright from '../playwright/index.ts'; import prettier from '../prettier/index.ts'; import routify from '../routify/index.ts'; import storybook from '../storybook/index.ts'; +import supabase from '../supabase/index.ts'; import tailwindcss from '../tailwindcss/index.ts'; import vitest from '../vitest/index.ts'; @@ -17,7 +18,7 @@ const categories = { testing: [vitest, playwright], css: [tailwindcss], db: [drizzle], - additional: [storybook, mdsvex, routify] + additional: [storybook, supabase, mdsvex, routify] }; export const adderCategories: AdderCategories = getCategoriesById(); diff --git a/packages/adders/supabase/config/adder.ts b/packages/adders/supabase/config/adder.ts index 2cd43e9f..d8531895 100644 --- a/packages/adders/supabase/config/adder.ts +++ b/packages/adders/supabase/config/adder.ts @@ -5,8 +5,8 @@ export const adder = defineAdderConfig({ metadata: { id: 'supabase', name: 'Supabase', - description: `Supabase is an open source Firebase alternative. -Start your project with a Postgres database, Authentication, instant APIs, Edge Functions, Realtime subscriptions, Storage, and Vector embeddings.`, + description: + 'Supabase is an open source Firebase alternative. Start your project with a Postgres database, Authentication, instant APIs, Edge Functions, Realtime subscriptions, Storage, and Vector embeddings.', environments: { svelte: false, kit: true }, website: { logo: './supabase.svg', @@ -32,14 +32,13 @@ Start your project with a Postgres database, Authentication, instant APIs, Edge condition: ({ options }) => options.cli } ], - // scripts: [ - // { - // description: 'Supabase CLI initialization', - // args: ['supabase', 'init', '--with-intellij-settings=false', '--with-vscode-settings=false'], - // type: 'dependency', - // condition: ({ options }) => options.cli - // } - // ], + scripts: [ + { + description: 'Supabase CLI initialization', + args: ['supabase', 'init', '--with-intellij-settings=false', '--with-vscode-settings=false'], + condition: ({ options }) => options.cli + } + ], files: [ { name: () => '.env', diff --git a/packages/cli/commands/add.ts b/packages/cli/commands/add.ts index 7b12d7bb..57c88bee 100644 --- a/packages/cli/commands/add.ts +++ b/packages/cli/commands/add.ts @@ -18,17 +18,20 @@ import { createWorkspace, findUp, installPackages, - TESTING + TESTING, + type Workspace } from '@svelte-cli/core/internal'; import type { AdderWithoutExplicitArgs, ExternalAdderConfig, InlineAdderConfig, OptionDefinition, - OptionValues + OptionValues, + Scripts } from '@svelte-cli/core'; import * as common from '../common.js'; import { Directive, downloadPackage, getPackageJSON } from '../utils/fetch-packages.js'; +import { COMMANDS, constructCommand } from 'package-manager-detector'; const AddersSchema = v.array(v.string()); const AdderOptionFlagsSchema = v.object({ @@ -242,7 +245,7 @@ export async function runAddCommand(options: Options, adders: string[]): Promise // prompt which adders to apply if (selectedAdders.length === 0) { const adderOptions: Record> = {}; - const workspace = createWorkspace(options.cwd); + const workspace = await createWorkspace(options.cwd); const projectType = workspace.kit ? 'kit' : 'svelte'; for (const { id, name } of Object.values(categories)) { const category = adderCategories[id]; @@ -284,7 +287,7 @@ export async function runAddCommand(options: Options, adders: string[]): Promise .filter((p) => p !== undefined); // add global checks - const { kit } = createWorkspace(options.cwd); + const { kit } = await createWorkspace(options.cwd); const projectType = kit ? 'kit' : 'svelte'; const adders = selectedAdders.map(({ adder }) => adder); const globalPreconditions = common.getGlobalPreconditions(options.cwd, projectType, adders); @@ -382,7 +385,7 @@ export async function runAddCommand(options: Options, adders: string[]): Promise } // format modified/created files with prettier (if available) - const workspace = createWorkspace(options.cwd); + const workspace = await createWorkspace(options.cwd); if (filesToFormat.length > 0 && depsStatus === 'installed' && workspace.prettier) { const { start, stop } = p.spinner(); start('Formatting modified files'); @@ -410,7 +413,8 @@ export async function runAddCommand(options: Options, adders: string[]): Promise options: official[metadata.id], cwd: options.cwd, colors: pc, - docs: metadata.website?.documentation + docs: metadata.website?.documentation, + packageManager: workspace.packageManager }); adderMessage += `- ${adderNextSteps.join('\n- ')}`; return adderMessage; @@ -464,7 +468,7 @@ export async function installAdders({ const filesToFormat = new Set(); for (const { config } of details) { const adderId = config.metadata.id; - const workspace = createWorkspace(cwd); + const workspace = await createWorkspace(cwd); workspace.options = official[adderId] ?? community[adderId]; @@ -473,6 +477,7 @@ export async function installAdders({ const pkgPath = installPackages(config, workspace); filesToFormat.add(pkgPath); const changedFiles = createOrUpdateFiles(config.files, workspace); + if (config.scripts) await runScripts(config.scripts, workspace); changedFiles.forEach((file) => filesToFormat.add(file)); } else if (config.integrationType === 'external') { await processExternalAdder(config, cwd); @@ -587,3 +592,33 @@ function getOptionChoices(details: AdderWithoutExplicitArgs) { } return { choices, defaults, groups }; } + +export async function runScripts( + scripts: Array>, + workspace: Workspace +) { + if (scripts.length < 1) return; + if (!workspace.packageManager) return; + + const loadingSpinner = p.spinner(); + loadingSpinner.start('Running scripts...'); + + for (const script of scripts) { + if (script.condition && !script.condition(workspace)) { + continue; + } + try { + const executeCommand = COMMANDS[workspace.packageManager].execute; + const { command, args } = constructCommand(executeCommand, script.args)!; + + await exec(command, args, { + nodeOptions: { cwd: workspace.cwd } + }); + } catch (error) { + const typedError = error as Error; + throw new Error(`Failed to execute scripts '${script.description}': ` + typedError.message); + } + } + + loadingSpinner.stop('Successfully executed scripts'); +} diff --git a/packages/cli/commands/create.ts b/packages/cli/commands/create.ts index 172db4bc..b4af56d9 100644 --- a/packages/cli/commands/create.ts +++ b/packages/cli/commands/create.ts @@ -12,6 +12,7 @@ import { } from '@svelte-cli/create'; import * as common from '../common.js'; import { runAddCommand } from './add.js'; +import { guessPackageManager } from '@svelte-cli/core/internal'; const langs = ['typescript', 'checkjs', 'none'] as const; const templateChoices = templates.map((t) => t.name); @@ -47,7 +48,7 @@ export const create = new Command('create') let i = 1; const initialSteps = []; const relative = path.relative(process.cwd(), directory); - const pm = await common.guessPackageManager(cwd); + const pm = await guessPackageManager(cwd); if (relative !== '') { initialSteps.push(`${i++}: ${highlight(`cd ${relative}`)}`); } diff --git a/packages/cli/common.ts b/packages/cli/common.ts index cebf2167..9bb3359e 100644 --- a/packages/cli/common.ts +++ b/packages/cli/common.ts @@ -6,6 +6,7 @@ import { detect, AGENTS, type AgentName } from 'package-manager-detector'; import { COMMANDS, constructCommand } from 'package-manager-detector/commands'; import type { AdderWithoutExplicitArgs } from '@svelte-cli/core'; import type { Argument, HelpConfiguration, Option } from 'commander'; +import { getUserAgent } from '@svelte-cli/core/internal'; export const helpConfig: HelpConfiguration = { argumentDescription: formatDescription, @@ -86,25 +87,6 @@ export async function suggestInstallingDependencies(cwd: string): Promise<'insta return 'installed'; } -/** - * Guesses the package manager based on the detected lockfile or user-agent. - * If neither of those return valid package managers, it falls back to `npm`. - */ -export async function guessPackageManager(cwd: string): Promise { - if (packageManager) return packageManager; - const pm = await detect({ cwd }); - return pm?.name ?? getUserAgent() ?? 'npm'; -} - -function getUserAgent() { - const userAgent = process.env.npm_config_user_agent; - if (!userAgent) return undefined; - const pmSpec = userAgent.split(' ')[0]; - const separatorPos = pmSpec.lastIndexOf('/'); - const name = pmSpec.substring(0, separatorPos) as AgentName; - return AGENTS.includes(name) ? name : undefined; -} - async function installDependencies(command: string, args: string[], cwd: string) { try { await exec(command, args, { nodeOptions: { cwd } }); diff --git a/packages/core/adder/config.ts b/packages/core/adder/config.ts index 4972aa5a..8aa1cd76 100644 --- a/packages/core/adder/config.ts +++ b/packages/core/adder/config.ts @@ -2,6 +2,7 @@ import type { OptionDefinition, OptionValues, Question } from './options.ts'; import type { FileType } from '../files/processors.ts'; import type { Workspace } from '../files/workspace.ts'; import type { Colors } from 'picocolors/types.ts'; +import type { AgentName } from 'package-manager-detector'; export type ConditionDefinition = ( Workspace: Workspace @@ -38,7 +39,6 @@ export type PackageDefinition = { export type Scripts = { description: string; args: string[]; - type: 'dependency' | 'external'; condition?: ConditionDefinition; }; @@ -52,13 +52,14 @@ export type BaseAdderConfig = { export type InlineAdderConfig = BaseAdderConfig & { integrationType: 'inline'; packages: Array>; + scripts?: Array>; files: Array>; nextSteps?: (data: { options: OptionValues; cwd: string; colors: Colors; docs: string | undefined; - packageManager: PackageManager; + packageManager: AgentName; }) => string[]; }; diff --git a/packages/core/adder/config.ts.rej b/packages/core/adder/config.ts.rej deleted file mode 100644 index d80df24c..00000000 --- a/packages/core/adder/config.ts.rej +++ /dev/null @@ -1,16 +0,0 @@ -diff a/packages/core/adder/config.ts b/packages/core/adder/config.ts (rejected hunks) -@@ -54,12 +62,14 @@ export type BaseAdderConfig = { - export type InlineAdderConfig = BaseAdderConfig & { - integrationType: 'inline'; - packages: PackageDefinition[]; -+ scripts?: Scripts[]; - files: FileTypes[]; - nextSteps?: (data: { - options: OptionValues; - cwd: string; - colors: Colors; - docs: string | undefined; -+ packageManager: PackageManager; - }) => string[]; - installHook?: (workspace: Workspace) => Promise; - uninstallHook?: (workspace: Workspace) => Promise; diff --git a/packages/core/adder/nextSteps.ts.rej b/packages/core/adder/nextSteps.ts.rej deleted file mode 100644 index aa9bf1cd..00000000 --- a/packages/core/adder/nextSteps.ts.rej +++ /dev/null @@ -1,9 +0,0 @@ -diff a/packages/core/adder/nextSteps.ts b/packages/core/adder/nextSteps.ts (rejected hunks) -@@ -28,6 +28,7 @@ export function displayNextSteps( - cwd: executionPlan.workingDirectory, - colors: pc, - docs: x.metadata.website?.documentation, -+ packageManager: executionPlan.packageManager, - }); - adderMessage += `- ${adderNextSteps.join('\n- ')}`; - return adderMessage; diff --git a/packages/core/adder/options.ts.rej b/packages/core/adder/options.ts.rej deleted file mode 100644 index d9dbc8d4..00000000 --- a/packages/core/adder/options.ts.rej +++ /dev/null @@ -1,25 +0,0 @@ -diff a/packages/core/adder/options.ts b/packages/core/adder/options.ts (rejected hunks) -@@ -24,6 +30,14 @@ export type SelectQuestion = { - options: PromptOption[]; - }; - -+type MultiSelectOption = { value: string; label: string; hint: string }; -+// eslint-disable-next-line @typescript-eslint/no-explicit-any -+export type MultiSelectQuestion = { -+ type: 'multiselect'; -+ default: Values; -+ options: MultiSelectOption[]; -+}; -+ - export type BaseQuestion = { - question: string; - // TODO: we want this to be akin to OptionValues so that the options can be inferred -@@ -297,6 +315,8 @@ export async function requestMissingOptionsFromUser = { - name: (options: Workspace) => string; diff --git a/packages/core/files/workspace.ts b/packages/core/files/workspace.ts index 5e7a687f..067d19c6 100644 --- a/packages/core/files/workspace.ts +++ b/packages/core/files/workspace.ts @@ -5,6 +5,7 @@ import { TESTING } from '../env.ts'; import { common, object } from '../tooling/js/index.ts'; import { commonFilePaths, findUp, getPackageJson, readFile } from './utils.ts'; import type { OptionDefinition, OptionValues, Question } from '../adder/options.ts'; +import { AGENTS, detect, type AgentName } from 'package-manager-detector'; export type Workspace = { options: OptionValues; @@ -13,6 +14,7 @@ export type Workspace = { prettier: boolean; typescript: boolean; kit: { libDirectory: string; routesDirectory: string } | undefined; + packageManager: AgentName; }; export type WorkspaceWithoutExplicitArgs = Workspace>; @@ -27,7 +29,9 @@ export function createEmptyWorkspace() { } as Workspace; } -export function createWorkspace(cwd: string): Workspace { +export async function createWorkspace( + cwd: string +): Promise> { const workspace = createEmptyWorkspace(); workspace.cwd = cwd; @@ -46,6 +50,7 @@ export function createWorkspace(cwd: string): Wor workspace.dependencies = { ...packageJson.devDependencies, ...packageJson.dependencies }; workspace.typescript = usesTypescript; workspace.prettier = 'prettier' in workspace.dependencies; + workspace.packageManager = await guessPackageManager(cwd); if ('@sveltejs/kit' in workspace.dependencies) workspace.kit = parseKitOptions(workspace); for (const [key, value] of Object.entries(workspace.dependencies)) { // removes the version ranges (e.g. `^` is removed from: `^9.0.0`) @@ -100,3 +105,22 @@ function parseKitOptions(workspace: WorkspaceWithoutExplicitArgs) { return { routesDirectory, libDirectory }; } + +/** + * Guesses the package manager based on the detected lockfile or user-agent. + * If neither of those return valid package managers, it falls back to `npm`. + */ +export async function guessPackageManager(cwd: string): Promise { + const pm = await detect({ cwd }); + return pm?.name ?? getUserAgent() ?? 'npm'; +} + +export function getUserAgent(): AgentName | undefined { + const userAgent = process.env.npm_config_user_agent; + if (!userAgent) return undefined; + + const pmSpec = userAgent.split(' ')[0]; + const separatorPos = pmSpec.lastIndexOf('/'); + const name = pmSpec.substring(0, separatorPos) as AgentName; + return AGENTS.includes(name) ? name : undefined; +} diff --git a/packages/core/internal.ts b/packages/core/internal.ts index 21db0bab..bff25e86 100644 --- a/packages/core/internal.ts +++ b/packages/core/internal.ts @@ -1,5 +1,10 @@ export { installPackages, findUp } from './files/utils.ts'; export { createOrUpdateFiles } from './files/processors.ts'; -export { createWorkspace, type Workspace } from './files/workspace.ts'; +export { + createWorkspace, + guessPackageManager, + getUserAgent, + type Workspace +} from './files/workspace.ts'; export { TESTING } from './env.ts'; export type { Question } from './adder/options.ts'; diff --git a/packages/core/package.json b/packages/core/package.json index a1ce9ec7..039e5a5c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -39,6 +39,7 @@ "@svelte-cli/clack-prompts": "workspace:*", "decircular": "^1.0.0", "dedent": "^1.5.3", + "package-manager-detector": "^0.2.0", "picocolors": "^1.1.0" }, "dependencies": { diff --git a/packages/core/utils/create-project.ts.rej b/packages/core/utils/create-project.ts.rej deleted file mode 100644 index 85334813..00000000 --- a/packages/core/utils/create-project.ts.rej +++ /dev/null @@ -1,8 +0,0 @@ -diff a/packages/core/utils/create-project.ts b/packages/core/utils/create-project.ts (rejected hunks) -@@ -127,6 +119,5 @@ export async function createProject(cwd: string, supportKit: boolean, supportSve - return { - projectCreated: true, - directory: path.join(process.cwd(), directory), -- packageManager, - }; - } diff --git a/packages/core/utils/dependencies.ts.rej b/packages/core/utils/dependencies.ts.rej deleted file mode 100644 index 47142d11..00000000 --- a/packages/core/utils/dependencies.ts.rej +++ /dev/null @@ -1,27 +0,0 @@ -diff a/packages/core/utils/dependencies.ts b/packages/core/utils/dependencies.ts (rejected hunks) -@@ -1,21 +1,21 @@ - import { selectPrompt } from './prompts'; --import { detect } from 'package-manager-detector'; - import { COMMANDS } from 'package-manager-detector/agents'; - import { spinner } from '@svelte-add/clack-prompts'; - import { executeCli } from './cli.js'; - --type PackageManager = (typeof packageManagers)[number] | undefined; -+export type PackageManager = (typeof packageManagers)[number] | undefined; - const packageManagers = ['npm', 'pnpm', 'yarn', 'bun'] as const; - - /** -+ * @param packageManager - * @param workingDirectory - * @returns the install status of dependencies - */ - export async function suggestInstallingDependencies( -+ packageManager: PackageManager, - workingDirectory: string, - ): Promise<'installed' | 'skipped'> { -- const detectedPm = await detect({ cwd: workingDirectory }); -- let selectedPm = detectedPm.agent; -+ let selectedPm = packageManager; - - selectedPm ??= await selectPrompt( - 'Which package manager do you want to install dependencies with?', diff --git a/packages/core/utils/workspace.ts.rej b/packages/core/utils/workspace.ts.rej deleted file mode 100644 index f95e9eb1..00000000 --- a/packages/core/utils/workspace.ts.rej +++ /dev/null @@ -1,28 +0,0 @@ -diff a/packages/core/utils/workspace.ts b/packages/core/utils/workspace.ts (rejected hunks) -@@ -5,6 +5,7 @@ import { getJsAstEditor } from '@svelte-add/ast-manipulation'; - import type { OptionDefinition, OptionValues, Question } from '../adder/options.js'; - import { remoteControl } from '../internal.js'; - import path from 'path'; -+import type { PackageManager } from './dependencies.js'; - - export type PrettierData = { - installed: boolean; -@@ -46,6 +48,7 @@ export function createEmptyWorkspace(): Workspace - routesDirectory: 'src/routes', - libDirectory: 'src/lib', - }, -+ packageManager: undefined, - } as Workspace; - } - -@@ -73,8 +76,10 @@ export function addPropertyToWorkspaceOption( - export async function populateWorkspaceDetails( - workspace: WorkspaceWithoutExplicitArgs, - workingDirectory: string, -+ packageManager: PackageManager, - ) { - workspace.cwd = workingDirectory; -+ workspace.packageManager = packageManager; - - const tsConfigFileName = 'tsconfig.json'; - const viteConfigFileName = 'vite.config.ts'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 253b9085..111100aa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -203,6 +203,9 @@ importers: dedent: specifier: ^1.5.3 version: 1.5.3 + package-manager-detector: + specifier: ^0.2.0 + version: 0.2.0 picocolors: specifier: ^1.1.0 version: 1.1.0 From d53dd78b06d2df460383244913eeaeee93017242 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sat, 28 Sep 2024 18:55:08 +0200 Subject: [PATCH 31/76] make supabase adder work --- packages/adders/supabase/config/adder.ts | 4 ++-- packages/adders/supabase/config/options.ts | 2 +- packages/cli/commands/add.ts | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/adders/supabase/config/adder.ts b/packages/adders/supabase/config/adder.ts index d8531895..8ffdc8f4 100644 --- a/packages/adders/supabase/config/adder.ts +++ b/packages/adders/supabase/config/adder.ts @@ -223,7 +223,7 @@ export const adder = defineAdderConfig({ } }, { - name: ({ kit }) => `${kit}/+layout.svelte`, + name: ({ kit }) => `${kit?.routesDirectory}/+layout.svelte`, contentType: 'text', condition: ({ options }) => options.auth.length > 0, content: ({ typescript }) => { @@ -360,7 +360,7 @@ export const adder = defineAdderConfig({ } }, { - name: ({ kit }) => `${kit}/auth/+page.svelte`, + name: ({ kit }) => `${kit?.routesDirectory}/auth/+page.svelte`, contentType: 'text', condition: ({ options }) => options.auth.length > 0, content: ({ options, typescript }) => { diff --git a/packages/adders/supabase/config/options.ts b/packages/adders/supabase/config/options.ts index 044e5dc0..de45230d 100644 --- a/packages/adders/supabase/config/options.ts +++ b/packages/adders/supabase/config/options.ts @@ -34,7 +34,7 @@ export const options = defineAdderOptions({ default: false }, helpers: { - question: `Do you want to add Supabase helper scripts to your package.json? E.g., ${colors.yellow('db:reset')} and ${colors.yellow('db:migration "description"')}`, + question: `Do you want to add Supabase helper scripts to your package.json? E.g., ${colors.yellow('db:reset')} and ${colors.yellow('db:migration')}`, type: 'boolean', default: false, condition: ({ cli }) => cli === true diff --git a/packages/cli/commands/add.ts b/packages/cli/commands/add.ts index 57c88bee..abae4842 100644 --- a/packages/cli/commands/add.ts +++ b/packages/cli/commands/add.ts @@ -598,7 +598,6 @@ export async function runScripts( workspace: Workspace ) { if (scripts.length < 1) return; - if (!workspace.packageManager) return; const loadingSpinner = p.spinner(); loadingSpinner.start('Running scripts...'); @@ -612,7 +611,7 @@ export async function runScripts( const { command, args } = constructCommand(executeCommand, script.args)!; await exec(command, args, { - nodeOptions: { cwd: workspace.cwd } + nodeOptions: { cwd: workspace.cwd, stdio: 'inherit' } }); } catch (error) { const typedError = error as Error; From 91e6b91b5213b0ae96c5f0ac7debca7aa0db8004 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sat, 28 Sep 2024 19:14:19 +0200 Subject: [PATCH 32/76] remove external integration type and uses scripts instead --- packages/adders/drizzle/config/adder.ts | 1 - packages/adders/eslint/config/adder.ts | 1 - packages/adders/mdsvex/config/adder.ts | 1 - packages/adders/playwright/config/adder.ts | 1 - packages/adders/prettier/config/adder.ts | 1 - packages/adders/routify/config/adder.ts | 1 - packages/adders/storybook/config/adder.ts | 12 +++-- packages/adders/supabase/config/adder.ts | 2 +- packages/adders/tailwindcss/config/adder.ts | 1 - packages/adders/vitest/config/adder.ts | 1 - packages/cli/commands/add.ts | 59 ++++++++------------- packages/core/adder/config.ts | 18 +------ 12 files changed, 33 insertions(+), 66 deletions(-) diff --git a/packages/adders/drizzle/config/adder.ts b/packages/adders/drizzle/config/adder.ts index 389433cd..de45b846 100644 --- a/packages/adders/drizzle/config/adder.ts +++ b/packages/adders/drizzle/config/adder.ts @@ -21,7 +21,6 @@ export const adder = defineAdderConfig({ } }, options: availableOptions, - integrationType: 'inline', packages: [ { name: 'drizzle-orm', version: '^0.31.2', dev: false }, { name: 'drizzle-kit', version: '^0.22.0', dev: true }, diff --git a/packages/adders/eslint/config/adder.ts b/packages/adders/eslint/config/adder.ts index 5c7c1ad4..62cef84b 100644 --- a/packages/adders/eslint/config/adder.ts +++ b/packages/adders/eslint/config/adder.ts @@ -18,7 +18,6 @@ export const adder = defineAdderConfig({ } }, options, - integrationType: 'inline', packages: [ { name: 'eslint', version: '^9.7.0', dev: true }, { name: '@types/eslint', version: '^9.6.0', dev: true }, diff --git a/packages/adders/mdsvex/config/adder.ts b/packages/adders/mdsvex/config/adder.ts index c71e8ba8..b3608cc8 100644 --- a/packages/adders/mdsvex/config/adder.ts +++ b/packages/adders/mdsvex/config/adder.ts @@ -15,7 +15,6 @@ export const adder = defineAdderConfig({ } }, options, - integrationType: 'inline', packages: [{ name: 'mdsvex', version: '^0.11.2', dev: true }], files: [ { diff --git a/packages/adders/playwright/config/adder.ts b/packages/adders/playwright/config/adder.ts index 081c6f18..e14c39b2 100644 --- a/packages/adders/playwright/config/adder.ts +++ b/packages/adders/playwright/config/adder.ts @@ -17,7 +17,6 @@ export const adder = defineAdderConfig({ } }, options, - integrationType: 'inline', packages: [{ name: '@playwright/test', version: '^1.45.3', dev: true }], files: [ { diff --git a/packages/adders/prettier/config/adder.ts b/packages/adders/prettier/config/adder.ts index 355ea888..4db38180 100644 --- a/packages/adders/prettier/config/adder.ts +++ b/packages/adders/prettier/config/adder.ts @@ -15,7 +15,6 @@ export const adder = defineAdderConfig({ } }, options, - integrationType: 'inline', packages: [ { name: 'prettier', version: '^3.3.2', dev: true }, { name: 'prettier-plugin-svelte', version: '^3.2.6', dev: true }, diff --git a/packages/adders/routify/config/adder.ts b/packages/adders/routify/config/adder.ts index 6565038f..820475b5 100644 --- a/packages/adders/routify/config/adder.ts +++ b/packages/adders/routify/config/adder.ts @@ -16,7 +16,6 @@ export const adder = defineAdderConfig({ } }, options, - integrationType: 'inline', packages: [{ name: '@roxi/routify', version: 'next', dev: true }], files: [ { diff --git a/packages/adders/storybook/config/adder.ts b/packages/adders/storybook/config/adder.ts index 36503e13..342f564d 100644 --- a/packages/adders/storybook/config/adder.ts +++ b/packages/adders/storybook/config/adder.ts @@ -20,8 +20,14 @@ export const adder = defineAdderConfig({ documentation: 'https://storybook.js.org/docs/get-started' } }, - + packages: [], + scripts: [ + { + description: 'applies storybook', + args: ['storybook@latest', 'init', '--skip-install', '--no-dev'], + stdio: 'inherit' + } + ], options, - integrationType: 'external', - command: 'storybook@latest init --skip-install --no-dev' + files: [] }); diff --git a/packages/adders/supabase/config/adder.ts b/packages/adders/supabase/config/adder.ts index 8ffdc8f4..934cc261 100644 --- a/packages/adders/supabase/config/adder.ts +++ b/packages/adders/supabase/config/adder.ts @@ -15,7 +15,6 @@ export const adder = defineAdderConfig({ } }, options: availableOptions, - integrationType: 'inline', packages: [ { name: '@supabase/supabase-js', version: '^2.45.3', dev: false }, { @@ -36,6 +35,7 @@ export const adder = defineAdderConfig({ { description: 'Supabase CLI initialization', args: ['supabase', 'init', '--with-intellij-settings=false', '--with-vscode-settings=false'], + stdio: 'pipe', condition: ({ options }) => options.cli } ], diff --git a/packages/adders/tailwindcss/config/adder.ts b/packages/adders/tailwindcss/config/adder.ts index 7d4c050f..7b63d5ae 100644 --- a/packages/adders/tailwindcss/config/adder.ts +++ b/packages/adders/tailwindcss/config/adder.ts @@ -18,7 +18,6 @@ export const adder = defineAdderConfig({ } }, options, - integrationType: 'inline', packages: [ { name: 'tailwindcss', version: '^3.4.9', dev: true }, { name: 'autoprefixer', version: '^10.4.20', dev: true }, diff --git a/packages/adders/vitest/config/adder.ts b/packages/adders/vitest/config/adder.ts index a973b058..abc35325 100644 --- a/packages/adders/vitest/config/adder.ts +++ b/packages/adders/vitest/config/adder.ts @@ -15,7 +15,6 @@ export const adder = defineAdderConfig({ } }, options, - integrationType: 'inline', packages: [{ name: 'vitest', version: '^2.0.4', dev: true }], files: [ { diff --git a/packages/cli/commands/add.ts b/packages/cli/commands/add.ts index abae4842..b5943646 100644 --- a/packages/cli/commands/add.ts +++ b/packages/cli/commands/add.ts @@ -18,13 +18,10 @@ import { createWorkspace, findUp, installPackages, - TESTING, type Workspace } from '@svelte-cli/core/internal'; import type { AdderWithoutExplicitArgs, - ExternalAdderConfig, - InlineAdderConfig, OptionDefinition, OptionValues, Scripts @@ -400,8 +397,8 @@ export async function runAddCommand(options: Options, adders: string[]): Promise // print next steps const nextStepsMsg = selectedAdders - .filter(({ adder }) => adder.config.integrationType === 'inline' && adder.config.nextSteps) - .map(({ adder }) => adder.config as InlineAdderConfig) + .filter(({ adder }) => adder.config.nextSteps) + .map(({ adder }) => adder.config) .map((config) => { const metadata = config.metadata; let adderMessage = ''; @@ -473,42 +470,16 @@ export async function installAdders({ workspace.options = official[adderId] ?? community[adderId]; // execute adders - if (config.integrationType === 'inline') { - const pkgPath = installPackages(config, workspace); - filesToFormat.add(pkgPath); - const changedFiles = createOrUpdateFiles(config.files, workspace); - if (config.scripts) await runScripts(config.scripts, workspace); - changedFiles.forEach((file) => filesToFormat.add(file)); - } else if (config.integrationType === 'external') { - await processExternalAdder(config, cwd); - } else { - throw new Error('Unknown integration type'); - } + const pkgPath = installPackages(config, workspace); + filesToFormat.add(pkgPath); + const changedFiles = createOrUpdateFiles(config.files, workspace); + if (config.scripts) await runScripts(config.scripts, workspace); + changedFiles.forEach((file) => filesToFormat.add(file)); } return Array.from(filesToFormat); } -async function processExternalAdder( - config: ExternalAdderConfig, - cwd: string -) { - if (!TESTING) p.log.message(`Executing external command ${pc.gray(`(${config.metadata.id})`)}`); - - try { - await exec('npx', config.command.split(' '), { - nodeOptions: { - cwd, - env: Object.assign(process.env, config.environment ?? {}), - stdio: TESTING ? 'pipe' : 'inherit' - } - }); - } catch (error) { - const typedError = error as Error; - throw new Error('Failed executing external command: ' + typedError.message); - } -} - /** * Dedupes and transforms aliases into their respective adder id */ @@ -600,23 +571,35 @@ export async function runScripts( if (scripts.length < 1) return; const loadingSpinner = p.spinner(); - loadingSpinner.start('Running scripts...'); + const runningScriptsText = 'Running scripts...'; + loadingSpinner.start(runningScriptsText); for (const script of scripts) { if (script.condition && !script.condition(workspace)) { continue; } + + if (script.stdio == 'inherit') { + // stop spinner as it will interfere with the script output + loadingSpinner.stop(runningScriptsText); + } + try { const executeCommand = COMMANDS[workspace.packageManager].execute; const { command, args } = constructCommand(executeCommand, script.args)!; await exec(command, args, { - nodeOptions: { cwd: workspace.cwd, stdio: 'inherit' } + nodeOptions: { cwd: workspace.cwd, stdio: script.stdio } }); } catch (error) { const typedError = error as Error; throw new Error(`Failed to execute scripts '${script.description}': ` + typedError.message); } + + if (script.stdio == 'inherit') { + // resume spinner as it will no longer interfere with the script output + loadingSpinner.start(runningScriptsText); + } } loadingSpinner.stop('Successfully executed scripts'); diff --git a/packages/core/adder/config.ts b/packages/core/adder/config.ts index 8aa1cd76..38f0ca19 100644 --- a/packages/core/adder/config.ts +++ b/packages/core/adder/config.ts @@ -39,18 +39,14 @@ export type PackageDefinition = { export type Scripts = { description: string; args: string[]; + stdio: 'inherit' | 'pipe'; condition?: ConditionDefinition; }; -export type BaseAdderConfig = { +export type AdderConfig = { metadata: AdderConfigMetadata; options: Args; runsAfter?: string[]; - integrationType: string; -}; - -export type InlineAdderConfig = BaseAdderConfig & { - integrationType: 'inline'; packages: Array>; scripts?: Array>; files: Array>; @@ -63,16 +59,6 @@ export type InlineAdderConfig = BaseAdderConfig string[]; }; -export type ExternalAdderConfig = BaseAdderConfig & { - integrationType: 'external'; - command: string; - environment?: Record; -}; - -export type AdderConfig = - | InlineAdderConfig - | ExternalAdderConfig; - export function defineAdderConfig( config: AdderConfig ): AdderConfig { From 8ca5a206f338ceed96fd73793d200fcf525d9d32 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sat, 28 Sep 2024 19:33:21 +0200 Subject: [PATCH 33/76] fix linting --- adder-template/src/config/adder.js | 1 - packages/core/files/utils.ts | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/adder-template/src/config/adder.js b/adder-template/src/config/adder.js index b2c5d5ec..e2c40954 100644 --- a/adder-template/src/config/adder.js +++ b/adder-template/src/config/adder.js @@ -10,7 +10,6 @@ export const adder = defineAdderConfig({ environments: { kit: true, svelte: true } }, options, - integrationType: 'inline', packages: [], files: [ { diff --git a/packages/core/files/utils.ts b/packages/core/files/utils.ts index 155c3873..c02cdbd8 100644 --- a/packages/core/files/utils.ts +++ b/packages/core/files/utils.ts @@ -1,7 +1,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { parseJson, serializeJson } from '@svelte-cli/ast-tooling'; -import type { InlineAdderConfig } from '../adder/config.ts'; +import type { AdderConfig } from '../adder/config.ts'; import type { OptionDefinition } from '../adder/options.ts'; import type { Workspace, WorkspaceWithoutExplicitArgs } from './workspace.ts'; @@ -52,7 +52,7 @@ export function readFile(workspace: WorkspaceWithoutExplicitArgs, filePath: stri } export function installPackages( - config: InlineAdderConfig, + config: AdderConfig, workspace: Workspace ): string { const { text: originalText, data } = getPackageJson(workspace); From c2ba9c5539de95220682f23ea8bcd971aa898344 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sun, 29 Sep 2024 19:34:26 +0200 Subject: [PATCH 34/76] avoid "Need to install the following packages:" message if using npx --- packages/cli/commands/add.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/cli/commands/add.ts b/packages/cli/commands/add.ts index b5943646..5d969818 100644 --- a/packages/cli/commands/add.ts +++ b/packages/cli/commands/add.ts @@ -588,6 +588,9 @@ export async function runScripts( const executeCommand = COMMANDS[workspace.packageManager].execute; const { command, args } = constructCommand(executeCommand, script.args)!; + // adding --yes as the first parameter helps avoiding the "Need to install the following packages:" message + if (workspace.packageManager == 'npm') args.unshift('--yes'); + await exec(command, args, { nodeOptions: { cwd: workspace.cwd, stdio: script.stdio } }); From 657a44f0db272fbb635dc1547228b85a29e3aa5c Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Mon, 30 Sep 2024 19:35:34 +0200 Subject: [PATCH 35/76] implement first review feedback --- packages/adders/_config/official.ts | 4 ++-- packages/adders/supabase/config/adder.ts | 7 +++---- packages/cli/commands/add.ts | 3 ++- packages/cli/commands/create.ts | 4 ++-- packages/core/adder/config.ts | 1 + packages/core/files/workspace.ts | 4 ++-- packages/core/internal.ts | 2 +- 7 files changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/adders/_config/official.ts b/packages/adders/_config/official.ts index 14ca6017..08f30e59 100644 --- a/packages/adders/_config/official.ts +++ b/packages/adders/_config/official.ts @@ -17,8 +17,8 @@ const categories = { codeQuality: [prettier, eslint], testing: [vitest, playwright], css: [tailwindcss], - db: [drizzle], - additional: [storybook, supabase, mdsvex, routify] + db: [drizzle, storybook], + additional: [supabase, mdsvex, routify] }; export const adderCategories: AdderCategories = getCategoriesById(); diff --git a/packages/adders/supabase/config/adder.ts b/packages/adders/supabase/config/adder.ts index 934cc261..01b1e63b 100644 --- a/packages/adders/supabase/config/adder.ts +++ b/packages/adders/supabase/config/adder.ts @@ -5,8 +5,7 @@ export const adder = defineAdderConfig({ metadata: { id: 'supabase', name: 'Supabase', - description: - 'Supabase is an open source Firebase alternative. Start your project with a Postgres database, Authentication, instant APIs, Edge Functions, Realtime subscriptions, Storage, and Vector embeddings.', + description: 'Supabase is an open source Firebase alternative.', environments: { svelte: false, kit: true }, website: { logo: './supabase.svg', @@ -967,7 +966,7 @@ export const adder = defineAdderConfig({ } } ], - nextSteps: ({ options, packageManager }) => { + nextSteps: ({ options, packageManager, workspace }) => { let command: string; if (!packageManager || packageManager === 'npm') { command = 'npx'; @@ -995,7 +994,7 @@ export const adder = defineAdderConfig({ if (isBasic || isMagicLink || isOAuth) { steps.push(dedent` - Update authGuard in ${colors.green('./src/hooks.server.js/ts')} with your protected routes`); + Update authGuard in ${colors.green(`./src/hooks.server.${workspace.typescript ? 'ts' : 'js'}`)} with your protected routes`); } if (isBasic || isMagicLink) { diff --git a/packages/cli/commands/add.ts b/packages/cli/commands/add.ts index 5d969818..823082ae 100644 --- a/packages/cli/commands/add.ts +++ b/packages/cli/commands/add.ts @@ -411,7 +411,8 @@ export async function runAddCommand(options: Options, adders: string[]): Promise cwd: options.cwd, colors: pc, docs: metadata.website?.documentation, - packageManager: workspace.packageManager + packageManager: workspace.packageManager, + workspace }); adderMessage += `- ${adderNextSteps.join('\n- ')}`; return adderMessage; diff --git a/packages/cli/commands/create.ts b/packages/cli/commands/create.ts index b4af56d9..470321d6 100644 --- a/packages/cli/commands/create.ts +++ b/packages/cli/commands/create.ts @@ -12,7 +12,7 @@ import { } from '@svelte-cli/create'; import * as common from '../common.js'; import { runAddCommand } from './add.js'; -import { guessPackageManager } from '@svelte-cli/core/internal'; +import { detectPackageManager } from '@svelte-cli/core/internal'; const langs = ['typescript', 'checkjs', 'none'] as const; const templateChoices = templates.map((t) => t.name); @@ -48,7 +48,7 @@ export const create = new Command('create') let i = 1; const initialSteps = []; const relative = path.relative(process.cwd(), directory); - const pm = await guessPackageManager(cwd); + const pm = await detectPackageManager(cwd); if (relative !== '') { initialSteps.push(`${i++}: ${highlight(`cd ${relative}`)}`); } diff --git a/packages/core/adder/config.ts b/packages/core/adder/config.ts index 38f0ca19..f04ab101 100644 --- a/packages/core/adder/config.ts +++ b/packages/core/adder/config.ts @@ -56,6 +56,7 @@ export type AdderConfig = { colors: Colors; docs: string | undefined; packageManager: AgentName; + workspace: Workspace; }) => string[]; }; diff --git a/packages/core/files/workspace.ts b/packages/core/files/workspace.ts index 067d19c6..3c58e5a7 100644 --- a/packages/core/files/workspace.ts +++ b/packages/core/files/workspace.ts @@ -50,7 +50,7 @@ export async function createWorkspace( workspace.dependencies = { ...packageJson.devDependencies, ...packageJson.dependencies }; workspace.typescript = usesTypescript; workspace.prettier = 'prettier' in workspace.dependencies; - workspace.packageManager = await guessPackageManager(cwd); + workspace.packageManager = await detectPackageManager(cwd); if ('@sveltejs/kit' in workspace.dependencies) workspace.kit = parseKitOptions(workspace); for (const [key, value] of Object.entries(workspace.dependencies)) { // removes the version ranges (e.g. `^` is removed from: `^9.0.0`) @@ -110,7 +110,7 @@ function parseKitOptions(workspace: WorkspaceWithoutExplicitArgs) { * Guesses the package manager based on the detected lockfile or user-agent. * If neither of those return valid package managers, it falls back to `npm`. */ -export async function guessPackageManager(cwd: string): Promise { +export async function detectPackageManager(cwd: string): Promise { const pm = await detect({ cwd }); return pm?.name ?? getUserAgent() ?? 'npm'; } diff --git a/packages/core/internal.ts b/packages/core/internal.ts index bff25e86..7df3e45d 100644 --- a/packages/core/internal.ts +++ b/packages/core/internal.ts @@ -2,7 +2,7 @@ export { installPackages, findUp } from './files/utils.ts'; export { createOrUpdateFiles } from './files/processors.ts'; export { createWorkspace, - guessPackageManager, + detectPackageManager, getUserAgent, type Workspace } from './files/workspace.ts'; From 0057e25cb47b2329a21dc627de0012e4ca3e2270 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Wed, 2 Oct 2024 18:12:12 +0200 Subject: [PATCH 36/76] fix categories --- packages/adders/_config/official.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/adders/_config/official.ts b/packages/adders/_config/official.ts index 08f30e59..cf4e7f64 100644 --- a/packages/adders/_config/official.ts +++ b/packages/adders/_config/official.ts @@ -17,8 +17,8 @@ const categories = { codeQuality: [prettier, eslint], testing: [vitest, playwright], css: [tailwindcss], - db: [drizzle, storybook], - additional: [supabase, mdsvex, routify] + db: [drizzle, supabase], + additional: [storybook, mdsvex, routify] }; export const adderCategories: AdderCategories = getCategoriesById(); From 527f29f8e0991e3bc0bbf9966bbea530ef035811 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Wed, 2 Oct 2024 18:25:10 +0200 Subject: [PATCH 37/76] fix errors --- packages/cli/commands/add.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/cli/commands/add.ts b/packages/cli/commands/add.ts index a8bf9552..7aa95e1f 100644 --- a/packages/cli/commands/add.ts +++ b/packages/cli/commands/add.ts @@ -285,19 +285,18 @@ export async function runAddCommand(options: Options, adders: string[]): Promise (dep) => !selectedAdders.some((a) => a.adder.config.metadata.id === dep) ) ?? []; - const workspace = createWorkspace(options.cwd); + const workspace = await createWorkspace(options.cwd); for (const depId of dependents) { const dependent = adderDetails.find((a) => a.config.metadata.id === depId); if (!dependent) throw new Error(`Adder '${name}' depends on an invalid '${depId}'`); // check if the dependent adder has already been installed let installed = false; - if (dependent.config.integrationType === 'inline') { - installed = dependent.config.packages.every( - // we'll skip the conditions since we don't have any options to supply it - (p) => p.condition !== undefined || !!workspace.dependencies[p.name] - ); - } + installed = dependent.config.packages.every( + // we'll skip the conditions since we don't have any options to supply it + (p) => p.condition !== undefined || !!workspace.dependencies[p.name] + ); + if (installed) continue; // prompt to install the dependent From 6d0be37c6dc7d55e8c58cad47570f7ea71f740f5 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Wed, 2 Oct 2024 18:51:17 +0200 Subject: [PATCH 38/76] fix indendation --- packages/adders/supabase/config/adder.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/adders/supabase/config/adder.ts b/packages/adders/supabase/config/adder.ts index 01b1e63b..fc3274b7 100644 --- a/packages/adders/supabase/config/adder.ts +++ b/packages/adders/supabase/config/adder.ts @@ -153,8 +153,8 @@ export const adder = defineAdderConfig({ interface PageData { session: Session | null } - // interface PageState {} - // interface Platform {} + // interface PageState {} + // interface Platform {} } } @@ -178,16 +178,16 @@ export const adder = defineAdderConfig({ const supabase = isBrowser() ? createBrowserClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { - global: { fetch }, + global: { fetch } }) : createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { global: { fetch }, - cookies: { - getAll() { - return data.cookies - }, - }, - }) + cookies: { + getAll() { + return data.cookies; + } + } + }); const { data: { session }, From 7a4e70a58a2d3da9c2dafa0741049831f9947ee6 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Wed, 2 Oct 2024 20:18:17 +0200 Subject: [PATCH 39/76] use svelte 5 syntax --- packages/adders/supabase/config/adder.ts | 41 +++++++++++++----------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/packages/adders/supabase/config/adder.ts b/packages/adders/supabase/config/adder.ts index fc3274b7..81af311b 100644 --- a/packages/adders/supabase/config/adder.ts +++ b/packages/adders/supabase/config/adder.ts @@ -33,7 +33,13 @@ export const adder = defineAdderConfig({ scripts: [ { description: 'Supabase CLI initialization', - args: ['supabase', 'init', '--with-intellij-settings=false', '--with-vscode-settings=false'], + args: [ + 'supabase', + 'init', + '--force', + '--with-intellij-settings=false', + '--with-vscode-settings=false' + ], stdio: 'pipe', condition: ({ options }) => options.cli } @@ -231,8 +237,9 @@ export const adder = defineAdderConfig({ import { invalidate } from '$app/navigation'; import { onMount } from 'svelte'; - export let data; - $: ({ session, supabase } = data); + /** @type {{data: any}} */ + let { children, data } = $props(); + let { session, supabase } = $derived(data); onMount(() => { const { data } = supabase.auth.onAuthStateChange((_, newSession) => { @@ -245,7 +252,7 @@ export const adder = defineAdderConfig({ }); - + {@render children?.()} `; } }, @@ -295,7 +302,7 @@ export const adder = defineAdderConfig({ ${ isDemo ? `// Redirect to local Inbucket for demo purposes - redirect(303, \`http://localhost:54324/m/\${email}\`)` + redirect(303, \`http://localhost:54324/m/\${email}\`)` : `return { message: 'Sign up succeeded! Please check your email inbox.' }` } } @@ -788,7 +795,7 @@ export const adder = defineAdderConfig({ import { page } from "$app/stores"; - $: logout = async () => { + async function logout() { const { error } = await $page.data.supabase.auth.signOut(); if (error) { console.error(error); @@ -832,10 +839,10 @@ export const adder = defineAdderConfig({ content: () => { return dedent`
    @@ -416,14 +384,6 @@ export const adder = defineAdderConfig({ : '' } ${isMagicLink ? '' : ''} - ${ - isOAuth - ? ` - - - ` - : '' - }
    @@ -594,36 +554,6 @@ export const adder = defineAdderConfig({ `; } }, - // OAuth only - { - name: ({ kit, typescript }) => - `${kit?.routesDirectory}/auth/callback/+server.${typescript ? 'ts' : 'js'}`, - contentType: 'text', - condition: ({ options }) => options.auth.includes('oauth'), - content: ({ typescript }) => { - const isTs = typescript; - - return dedent` - import { error, redirect } from '@sveltejs/kit' - ${isTs ? `import type { RequestHandler } from './$types'` : ''} - - export const GET${isTs ? ': RequestHandler' : ''} = async ({ url, locals: { supabase } }) => { - const code = url.searchParams.get('code')${isTs ? ' as string' : ''} - const next = url.searchParams.get('next') ?? '/' - - if (code) { - const { error: authError } = await supabase.auth.exchangeCodeForSession(code) - if (authError) { - console.error(authError) - error(500, 'Something went wrong, please try again.') - } - } - - throw redirect(303, \`/\${next.slice(1)}\`) - } - `; - } - }, // Admin client helper { name: ({ kit, typescript }) => @@ -678,7 +608,6 @@ export const adder = defineAdderConfig({ content: ({ content, options }) => { const isBasic = options.auth.includes('basic'); const isMagicLink = options.auth.includes('magicLink'); - const isOAuth = options.auth.includes('oauth'); content = content.replace('"http://127.0.0.1:3000"', '"http://localhost:5173"'); content = content.replace('"https://127.0.0.1:3000"', '"https://localhost:5173/*"'); @@ -711,20 +640,6 @@ export const adder = defineAdderConfig({ ` ); } - if (isOAuth) { - content = appendContent( - content, - dedent` - \n# Local Google auth configuration - [auth.external.google] - enabled = true - client_id = "env(SUPABASE_AUTH_EXTERNAL_GOOGLE_CLIENT_ID)" - secret = "env(SUPABASE_AUTH_EXTERNAL_GOOGLE_SECRET)" - redirect_uri = "http://127.0.0.1:54321/auth/v1/callback" - skip_nonce_check = true - ` - ); - } return content; } @@ -982,7 +897,6 @@ export const adder = defineAdderConfig({ const { auth, cli: isCli, helpers: isHelpers } = options; const isBasic = auth.includes('basic'); const isMagicLink = auth.includes('magicLink'); - const isOAuth = auth.includes('oauth'); const steps = ['Visit the Supabase docs: https://supabase.com/docs']; @@ -997,7 +911,7 @@ export const adder = defineAdderConfig({ Check out ${colors.green('package.json')} for the helper scripts. Remember to generate your database types`); } - if (isBasic || isMagicLink || isOAuth) { + if (isBasic || isMagicLink) { steps.push(dedent` Update authGuard in ${colors.green(`./src/hooks.server.${workspace.typescript ? 'ts' : 'js'}`)} with your protected routes`); } @@ -1010,27 +924,12 @@ export const adder = defineAdderConfig({ } } - if (isOAuth) { - steps.push(dedent` - ${colors.bold('OAuth:')} Refer to the docs for other OAuth providers: https://supabase.com/docs/guides/auth/social-login`); - steps.push(dedent` - ${colors.bold('OAuth:')} Enable Google in your hosted project dashboard and populate with your application's Google OAuth credentials. Create them via: https://console.cloud.google.com/apis/credentials/consent`); - - if (isCli) { - steps.push(dedent` - ${colors.bold('OAuth (Local Dev):')} Add your application's Google OAuth credentials to ${colors.green('.env')}. Create them via: https://console.cloud.google.com/apis/credentials/consent`); - steps.push(dedent` - ${colors.bold('OAuth (Local Dev):')} To enable other local providers (or disable Google) update ${colors.green('./supabase/config.toml')} and restart the local Supabase services`); - } - } - return steps; } }); function generateEnvFileContent({ content, options }: TextFileEditor) { const isCli = options.cli; - const isOAuth = options.auth.includes('oauth'); content = addEnvVar(content, 'PUBLIC_BASE_URL', '"http://localhost:5173"'); content = addEnvVar( @@ -1059,19 +958,6 @@ function generateEnvFileContent({ content, options }: TextFileEditor Date: Thu, 3 Oct 2024 08:46:08 +0200 Subject: [PATCH 42/76] temp --- packages/adders/common.ts | 77 ++++++++++++++- packages/adders/lucia/config/adder.ts | 69 +------------ packages/adders/supabase/config/adder.ts | 119 ++++++++++++++++++----- 3 files changed, 173 insertions(+), 92 deletions(-) diff --git a/packages/adders/common.ts b/packages/adders/common.ts index 4255e8fe..9f3551ef 100644 --- a/packages/adders/common.ts +++ b/packages/adders/common.ts @@ -1,5 +1,5 @@ import { imports, exports, common } from '@svelte-cli/core/js'; -import type { ScriptFileEditor } from '@svelte-cli/core'; +import { Walker, type AstTypes, type ScriptFileEditor } from '@svelte-cli/core'; import type { Question } from '@svelte-cli/core/internal'; export function addEslintConfigPrettier({ ast }: ScriptFileEditor>) { @@ -57,3 +57,78 @@ export function addEslintConfigPrettier({ ast }: ScriptFileEditor n.type === 'TSModuleDeclaration') + .find((m) => m.global && m.declare); + + if (globalDecl?.body?.type !== 'TSModuleBlock') { + throw new Error('Unexpected body type of `declare global` in `src/app.d.ts`'); + } + + if (!globalDecl) { + const decl = common.statementFromString(` + declare global { + namespace App { + interface Locals { + user: import('lucia').User | null; + session: import('lucia').Session | null; + } + } + }`); + ast.body.push(decl); + return; + } + + let app: AstTypes.TSModuleDeclaration | undefined; + let locals: AstTypes.TSInterfaceDeclaration | undefined; + + // prettier-ignore + Walker.walk(globalDecl as AstTypes.ASTNode, {}, { + TSModuleDeclaration(node, { next }) { + if (node.id.type === 'Identifier' && node.id.name === 'App') { + app = node; + } + next(); + }, + TSInterfaceDeclaration(node) { + if (node.id.type === 'Identifier' && node.id.name === 'Locals') { + locals = node; + } + }, + }); + + if (!app) { + app ??= common.statementFromString(` + namespace App { + interface Locals { + user: import('lucia').User | null; + session: import('lucia').Session | null; + } + }`) as AstTypes.TSModuleDeclaration; + globalDecl.body.body.push(app); + return; + } + + if (app.body?.type !== 'TSModuleBlock') { + throw new Error('Unexpected body type of `namespace App` in `src/app.d.ts`'); + } + + if (!locals) { + // add Locals interface it if it's missing + locals = common.statementFromString('interface Locals {}') as AstTypes.TSInterfaceDeclaration; + app.body.body.push(locals); + } + + return locals; +} + +export function hasTypeProp( + name: string, + node: AstTypes.TSInterfaceDeclaration['body']['body'][number] +) { + return ( + node.type === 'TSPropertySignature' && node.key.type === 'Identifier' && node.key.name === name + ); +} diff --git a/packages/adders/lucia/config/adder.ts b/packages/adders/lucia/config/adder.ts index b58769b1..51990cd7 100644 --- a/packages/adders/lucia/config/adder.ts +++ b/packages/adders/lucia/config/adder.ts @@ -3,6 +3,7 @@ import { colors, dedent, defineAdderConfig, log, Walker } from '@svelte-cli/core import { common, exports, imports, variables, object, functions } from '@svelte-cli/core/js'; // eslint-disable-next-line no-duplicate-imports import type { AstKinds, AstTypes } from '@svelte-cli/core/js'; +import { getOrCreateAppLocalsInterface, hasTypeProp } from '../../common.ts'; const LUCIA_ADAPTER = { mysql: 'DrizzleMySQLAdapter', @@ -238,68 +239,10 @@ export const adder = defineAdderConfig({ condition: ({ typescript }) => typescript, contentType: 'script', content: ({ ast }) => { - const globalDecl = ast.body - .filter((n) => n.type === 'TSModuleDeclaration') - .find((m) => m.global && m.declare); - - if (globalDecl?.body?.type !== 'TSModuleBlock') { - throw new Error('Unexpected body type of `declare global` in `src/app.d.ts`'); - } - - if (!globalDecl) { - const decl = common.statementFromString(` - declare global { - namespace App { - interface Locals { - user: import('lucia').User | null; - session: import('lucia').Session | null; - } - } - }`); - ast.body.push(decl); - return; - } - - let app: AstTypes.TSModuleDeclaration | undefined; - let locals: AstTypes.TSInterfaceDeclaration | undefined; - - // prettier-ignore - Walker.walk(globalDecl as AstTypes.ASTNode, {}, { - TSModuleDeclaration(node, { next }) { - if (node.id.type === 'Identifier' && node.id.name === 'App') { - app = node; - } - next(); - }, - TSInterfaceDeclaration(node) { - if (node.id.type === 'Identifier' && node.id.name === 'Locals') { - locals = node; - } - }, - }); - - if (!app) { - app ??= common.statementFromString(` - namespace App { - interface Locals { - user: import('lucia').User | null; - session: import('lucia').Session | null; - } - }`) as AstTypes.TSModuleDeclaration; - globalDecl.body.body.push(app); - return; - } - - if (app.body?.type !== 'TSModuleBlock') { - throw new Error('Unexpected body type of `namespace App` in `src/app.d.ts`'); - } + const locals = getOrCreateAppLocalsInterface(ast); if (!locals) { - // add Locals interface it if it's missing - locals = common.statementFromString( - 'interface Locals {}' - ) as AstTypes.TSInterfaceDeclaration; - app.body.body.push(locals); + throw new Error('Failed detecting `locals` interface in `src/app.d.ts`'); } const user = locals.body.body.find((prop) => hasTypeProp('user', prop)); @@ -768,12 +711,6 @@ function createLuciaType(name: string): AstTypes.TSInterfaceBody['body'][number] }; } -function hasTypeProp(name: string, node: AstTypes.TSInterfaceDeclaration['body']['body'][number]) { - return ( - node.type === 'TSPropertySignature' && node.key.type === 'Identifier' && node.key.name === name - ); -} - function usingSequence(node: AstTypes.VariableDeclarator, handleName: string) { return ( node.id.type === 'Identifier' && diff --git a/packages/adders/supabase/config/adder.ts b/packages/adders/supabase/config/adder.ts index 2154fa11..1030f18e 100644 --- a/packages/adders/supabase/config/adder.ts +++ b/packages/adders/supabase/config/adder.ts @@ -1,5 +1,13 @@ -import { defineAdderConfig, dedent, type TextFileEditor, colors } from '@svelte-cli/core'; +import { + defineAdderConfig, + dedent, + type TextFileEditor, + colors, + type AstTypes +} from '@svelte-cli/core'; import { options as availableOptions } from './options.ts'; +import { getOrCreateAppLocalsInterface, hasTypeProp } from '../../common.ts'; +import { imports } from '@svelte-cli/core/js'; export const adder = defineAdderConfig({ metadata: { @@ -70,7 +78,6 @@ export const adder = defineAdderConfig({ ${isTs && isDemo ? `import { type Handle, redirect } from '@sveltejs/kit'` : ''} ${isTs && !isDemo ? `import type { Handle } from '@sveltejs/kit'` : ''} import { sequence } from '@sveltejs/kit/hooks' - import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public' const supabase${isTs ? ': Handle' : ''} = async ({ event, resolve }) => { @@ -139,33 +146,58 @@ export const adder = defineAdderConfig({ }, { name: () => './src/app.d.ts', - contentType: 'text', + contentType: 'script', condition: ({ options, typescript }) => typescript && options.auth.length > 0, - content: ({ options }) => { + content: ({ ast, options, typescript }) => { const { cli: isCli, helpers: isHelpers } = options; - return dedent` - import type { Session, SupabaseClient, User } from '@supabase/supabase-js' - ${isCli && isHelpers ? `import type { Database } from '$lib/supabase-types'\n` : ''} - declare global { - namespace App { - // interface Error {} - interface Locals { - supabase: SupabaseClient${isCli && isHelpers ? '' : ''} - safeGetSession: () => Promise<{ session: Session | null; user: User | null }> - session: Session | null - user: User | null - } - interface PageData { - session: Session | null - } - // interface PageState {} - // interface Platform {} - } - } + imports.addNamed( + ast, + '@supabase/supabase-js', + { + Session: 'Session', + SupabaseClient: 'SupabaseClient', + User: 'User' + }, + true + ); + + if (isCli && isHelpers) + imports.addNamed(ast, '$lib/supabase-types', { Database: 'Database' }, true); + + const locals = getOrCreateAppLocalsInterface(ast); + if (!locals) { + throw new Error('Failed detecting `locals` interface in `src/app.d.ts`'); + } - export {} - `; + const supabase = locals.body.body.find((prop) => hasTypeProp('supabase', prop)); + + if (!supabase) { + locals.body.body.push(createSupabaseType('supabase', typescript)); + } + + // return dedent` + // import type { Session, SupabaseClient, User } from '@supabase/supabase-js' + // ${isCli && isHelpers ? `import type { Database } from '$lib/supabase-types'\n` : ''} + // declare global { + // namespace App { + // // interface Error {} + // interface Locals { + // supabase: SupabaseClient${isCli && isHelpers ? '' : ''} + // safeGetSession: () => Promise<{ session: Session | null; user: User | null }> + // session: Session | null + // user: User | null + // } + // interface PageData { + // session: Session | null + // } + // // interface PageState {} + // // interface Platform {} + // } + // } + + // export {} + // `; } }, { @@ -972,3 +1004,40 @@ function appendContent(existing: string, content: string) { const withNewLine = !existing.length || existing.endsWith('\n') ? existing : existing + '\n'; return withNewLine + content + '\n'; } + +function createSupabaseType( + name: string, + typescript: boolean +): AstTypes.TSInterfaceBody['body'][number] { + return { + type: 'TSPropertySignature', + key: { + type: 'Identifier', + name + }, + typeAnnotation: { + type: 'TSTypeAnnotation', + typeAnnotation: { + type: 'TSTypeReference', + typeName: { + type: 'Identifier', + name: 'SupabaseClient' + }, + typeParameters: typescript + ? { + type: 'TSTypeParameterInstantiation', + params: [ + { + type: 'TSTypeReference', + typeName: { + type: 'Identifier', + name: 'Database' + } + } + ] + } + : undefined + } + } + }; +} From 7514f24c38864c7d06d0efde85053c0e8d0e3127 Mon Sep 17 00:00:00 2001 From: Manuel <30698007+manuel3108@users.noreply.github.com> Date: Thu, 3 Oct 2024 09:52:16 +0200 Subject: [PATCH 43/76] Improve `app.d.ts` robustnes --- packages/adders/common.ts | 73 ++++++------ packages/adders/lucia/config/adder.ts | 4 +- packages/adders/supabase/config/adder.ts | 135 ++++++++++++++++++----- 3 files changed, 144 insertions(+), 68 deletions(-) diff --git a/packages/adders/common.ts b/packages/adders/common.ts index 9f3551ef..d54dcdb8 100644 --- a/packages/adders/common.ts +++ b/packages/adders/common.ts @@ -58,70 +58,65 @@ export function addEslintConfigPrettier({ ast }: ScriptFileEditor n.type === 'TSModuleDeclaration') .find((m) => m.global && m.declare); - if (globalDecl?.body?.type !== 'TSModuleBlock') { + if (globalDecl && globalDecl?.body?.type !== 'TSModuleBlock') { throw new Error('Unexpected body type of `declare global` in `src/app.d.ts`'); } if (!globalDecl) { const decl = common.statementFromString(` - declare global { - namespace App { - interface Locals { - user: import('lucia').User | null; - session: import('lucia').Session | null; - } - } - }`); + declare global {}`) as AstTypes.TSModuleDeclaration; ast.body.push(decl); - return; + globalDecl = decl; + } + + if (!globalDecl || !globalDecl.body || !globalDecl.body.body) { + throw new Error('Failed processing global declaration'); } let app: AstTypes.TSModuleDeclaration | undefined; - let locals: AstTypes.TSInterfaceDeclaration | undefined; + let interfaceNode: AstTypes.TSInterfaceDeclaration | undefined; // prettier-ignore Walker.walk(globalDecl as AstTypes.ASTNode, {}, { - TSModuleDeclaration(node, { next }) { - if (node.id.type === 'Identifier' && node.id.name === 'App') { - app = node; - } - next(); - }, - TSInterfaceDeclaration(node) { - if (node.id.type === 'Identifier' && node.id.name === 'Locals') { - locals = node; - } - }, - }); + TSModuleDeclaration(node, { next }) { + if (node.id.type === 'Identifier' && node.id.name === 'App') { + app = node; + } + next(); + }, + TSInterfaceDeclaration(node) { + if (node.id.type === 'Identifier' && node.id.name === name) { + interfaceNode = node; + } + }, + }); if (!app) { - app ??= common.statementFromString(` - namespace App { - interface Locals { - user: import('lucia').User | null; - session: import('lucia').Session | null; - } - }`) as AstTypes.TSModuleDeclaration; - globalDecl.body.body.push(app); - return; + app ??= common.statementFromString('namespace App {}') as AstTypes.TSModuleDeclaration; + (globalDecl.body as AstTypes.TSModuleBlock).body.push(app); } if (app.body?.type !== 'TSModuleBlock') { throw new Error('Unexpected body type of `namespace App` in `src/app.d.ts`'); } - if (!locals) { - // add Locals interface it if it's missing - locals = common.statementFromString('interface Locals {}') as AstTypes.TSInterfaceDeclaration; - app.body.body.push(locals); + if (!interfaceNode) { + // add interface it if it's missing + interfaceNode = common.statementFromString( + `interface ${name} {}` + ) as AstTypes.TSInterfaceDeclaration; + app.body.body.push(interfaceNode); } - return locals; + return interfaceNode; } export function hasTypeProp( diff --git a/packages/adders/lucia/config/adder.ts b/packages/adders/lucia/config/adder.ts index 51990cd7..48bbb212 100644 --- a/packages/adders/lucia/config/adder.ts +++ b/packages/adders/lucia/config/adder.ts @@ -3,7 +3,7 @@ import { colors, dedent, defineAdderConfig, log, Walker } from '@svelte-cli/core import { common, exports, imports, variables, object, functions } from '@svelte-cli/core/js'; // eslint-disable-next-line no-duplicate-imports import type { AstKinds, AstTypes } from '@svelte-cli/core/js'; -import { getOrCreateAppLocalsInterface, hasTypeProp } from '../../common.ts'; +import { getOrCreateAppInterface, hasTypeProp } from '../../common.ts'; const LUCIA_ADAPTER = { mysql: 'DrizzleMySQLAdapter', @@ -239,7 +239,7 @@ export const adder = defineAdderConfig({ condition: ({ typescript }) => typescript, contentType: 'script', content: ({ ast }) => { - const locals = getOrCreateAppLocalsInterface(ast); + const locals = getOrCreateAppInterface(ast, 'Locals'); if (!locals) { throw new Error('Failed detecting `locals` interface in `src/app.d.ts`'); diff --git a/packages/adders/supabase/config/adder.ts b/packages/adders/supabase/config/adder.ts index 1030f18e..640f7bd3 100644 --- a/packages/adders/supabase/config/adder.ts +++ b/packages/adders/supabase/config/adder.ts @@ -6,7 +6,7 @@ import { type AstTypes } from '@svelte-cli/core'; import { options as availableOptions } from './options.ts'; -import { getOrCreateAppLocalsInterface, hasTypeProp } from '../../common.ts'; +import { getOrCreateAppInterface, hasTypeProp } from '../../common.ts'; import { imports } from '@svelte-cli/core/js'; export const adder = defineAdderConfig({ @@ -165,39 +165,28 @@ export const adder = defineAdderConfig({ if (isCli && isHelpers) imports.addNamed(ast, '$lib/supabase-types', { Database: 'Database' }, true); - const locals = getOrCreateAppLocalsInterface(ast); + const locals = getOrCreateAppInterface(ast, 'Locals'); if (!locals) { throw new Error('Failed detecting `locals` interface in `src/app.d.ts`'); } const supabase = locals.body.body.find((prop) => hasTypeProp('supabase', prop)); - - if (!supabase) { - locals.body.body.push(createSupabaseType('supabase', typescript)); + const safeGetSession = locals.body.body.find((prop) => hasTypeProp('safeGetSession', prop)); + const session = locals.body.body.find((prop) => hasTypeProp('session', prop)); + const user = locals.body.body.find((prop) => hasTypeProp('user', prop)); + + if (!supabase) locals.body.body.push(createSupabaseType('supabase', typescript)); + if (!safeGetSession) locals.body.body.push(createSafeGetSessionType('safeGetSession')); + if (!session) locals.body.body.push(createSessionType('session')); + if (!user) locals.body.body.push(createUserType('user')); + + const pageData = getOrCreateAppInterface(ast, 'PageData'); + if (!pageData) { + throw new Error('Failed detecting `pageData` interface in `src/app.d.ts`'); } - // return dedent` - // import type { Session, SupabaseClient, User } from '@supabase/supabase-js' - // ${isCli && isHelpers ? `import type { Database } from '$lib/supabase-types'\n` : ''} - // declare global { - // namespace App { - // // interface Error {} - // interface Locals { - // supabase: SupabaseClient${isCli && isHelpers ? '' : ''} - // safeGetSession: () => Promise<{ session: Session | null; user: User | null }> - // session: Session | null - // user: User | null - // } - // interface PageData { - // session: Session | null - // } - // // interface PageState {} - // // interface Platform {} - // } - // } - - // export {} - // `; + const pageDataSession = pageData.body.body.find((prop) => hasTypeProp('session', prop)); + if (!pageDataSession) pageData.body.body.push(createSessionType('session')); } }, { @@ -1041,3 +1030,95 @@ function createSupabaseType( } }; } + +function createSafeGetSessionType(name: string): AstTypes.TSInterfaceBody['body'][number] { + return { + type: 'TSPropertySignature', + key: { + type: 'Identifier', + name + }, + typeAnnotation: { + type: 'TSTypeAnnotation', + typeAnnotation: { + type: 'TSFunctionType', + typeAnnotation: { + type: 'TSTypeAnnotation', + typeAnnotation: { + type: 'TSTypeReference', + typeName: { + type: 'Identifier', + name: 'Promise' + }, + typeParameters: { + type: 'TSTypeParameterInstantiation', + params: [ + { + type: 'TSTypeLiteral', + members: [createSessionType('session'), createUserType('user')] + } + ] + } + } + }, + parameters: [] + } + } + }; +} + +function createSessionType(name: string): AstTypes.TSPropertySignature { + return { + type: 'TSPropertySignature', + key: { + type: 'Identifier', + name + }, + typeAnnotation: { + type: 'TSTypeAnnotation', + typeAnnotation: { + type: 'TSUnionType', + types: [ + { + type: 'TSTypeReference', + typeName: { + type: 'Identifier', + name: 'Session' + } + }, + { + type: 'TSNullKeyword' + } + ] + } + } + }; +} + +function createUserType(name: string): AstTypes.TSPropertySignature { + return { + type: 'TSPropertySignature', + key: { + type: 'Identifier', + name + }, + typeAnnotation: { + type: 'TSTypeAnnotation', + typeAnnotation: { + type: 'TSUnionType', + types: [ + { + type: 'TSTypeReference', + typeName: { + type: 'Identifier', + name: 'User' + } + }, + { + type: 'TSNullKeyword' + } + ] + } + } + }; +} From 00f06f720370a56558ab5f55c2278fe241294f49 Mon Sep 17 00:00:00 2001 From: Manuel <30698007+manuel3108@users.noreply.github.com> Date: Thu, 3 Oct 2024 10:22:51 +0200 Subject: [PATCH 44/76] make `+layout.ts` more robust --- packages/adders/supabase/config/adder.ts | 33 +++++++++++++++++------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/packages/adders/supabase/config/adder.ts b/packages/adders/supabase/config/adder.ts index 640f7bd3..cbee2204 100644 --- a/packages/adders/supabase/config/adder.ts +++ b/packages/adders/supabase/config/adder.ts @@ -7,7 +7,7 @@ import { } from '@svelte-cli/core'; import { options as availableOptions } from './options.ts'; import { getOrCreateAppInterface, hasTypeProp } from '../../common.ts'; -import { imports } from '@svelte-cli/core/js'; +import { common, imports } from '@svelte-cli/core/js'; export const adder = defineAdderConfig({ metadata: { @@ -191,16 +191,28 @@ export const adder = defineAdderConfig({ }, { name: ({ kit, typescript }) => `${kit?.routesDirectory}/+layout.${typescript ? 'ts' : 'js'}`, - contentType: 'text', + contentType: 'script', condition: ({ options }) => options.auth.length > 0, - content: ({ typescript }) => { - const isTs = typescript; + content: ({ ast, typescript }) => { + imports.addNamed(ast, '@supabase/ssr', { + createBrowserClient: 'createBrowserClient', + createServerClient: 'createServerClient', + isBrowser: 'isBrowser' + }); + + imports.addNamed(ast, '$env/static/public', { + PUBLIC_SUPABASE_ANON_KEY: 'PUBLIC_SUPABASE_ANON_KEY', + PUBLIC_SUPABASE_URL: 'PUBLIC_SUPABASE_URL' + }); - return dedent` - import { createBrowserClient, createServerClient, isBrowser } from '@supabase/ssr' - import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public' - ${isTs ? `import type { LayoutLoad } from './$types'\n` : ''} - export const load${isTs ? ': LayoutLoad' : ''} = async ({ data, depends, fetch }) => { + if (typescript) { + imports.addNamed(ast, './$types', { LayoutLoad: 'LayoutLoad' }, true); + } + + common.addFromString( + ast, + dedent` + export const load${typescript ? ': LayoutLoad' : ''} = async ({ data, depends, fetch }) => { depends('supabase:auth') const supabase = isBrowser() @@ -226,7 +238,8 @@ export const adder = defineAdderConfig({ return { session, supabase, user } } - `; + ` + ); } }, { From bd5f1e974a155776facb215ce77e0f6484839e14 Mon Sep 17 00:00:00 2001 From: Manuel <30698007+manuel3108@users.noreply.github.com> Date: Thu, 3 Oct 2024 10:29:45 +0200 Subject: [PATCH 45/76] Make `+layout.server.ts` more robust --- packages/adders/supabase/config/adder.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/adders/supabase/config/adder.ts b/packages/adders/supabase/config/adder.ts index cbee2204..fcbda755 100644 --- a/packages/adders/supabase/config/adder.ts +++ b/packages/adders/supabase/config/adder.ts @@ -245,20 +245,24 @@ export const adder = defineAdderConfig({ { name: ({ kit, typescript }) => `${kit?.routesDirectory}/+layout.server.${typescript ? 'ts' : 'js'}`, - contentType: 'text', + contentType: 'script', condition: ({ options }) => options.auth.length > 0, - content: ({ typescript }) => { - const isTs = typescript; + content: ({ ast, typescript }) => { + if (typescript) { + imports.addNamed(ast, './$types', { LayoutServerLoad: 'LayoutServerLoad' }, true); + } - return dedent` - ${isTs ? `import type { LayoutServerLoad } from './$types'\n` : ''} - export const load${isTs ? ': LayoutServerLoad' : ''} = async ({ locals: { session }, cookies }) => { + common.addFromString( + ast, + dedent` + export const load${typescript ? ': LayoutServerLoad' : ''} = async ({ locals: { session }, cookies }) => { return { session, cookies: cookies.getAll(), } } - `; + ` + ); } }, { From 5f24341ae1d8f2d9d38b61e1689d441df7c259f3 Mon Sep 17 00:00:00 2001 From: Manuel <30698007+manuel3108@users.noreply.github.com> Date: Thu, 3 Oct 2024 11:08:56 +0200 Subject: [PATCH 46/76] improve robustnes for common files --- packages/adders/supabase/config/adder.ts | 117 +++++++++++++---------- 1 file changed, 67 insertions(+), 50 deletions(-) diff --git a/packages/adders/supabase/config/adder.ts b/packages/adders/supabase/config/adder.ts index fcbda755..8d3b297c 100644 --- a/packages/adders/supabase/config/adder.ts +++ b/packages/adders/supabase/config/adder.ts @@ -8,6 +8,7 @@ import { import { options as availableOptions } from './options.ts'; import { getOrCreateAppInterface, hasTypeProp } from '../../common.ts'; import { common, imports } from '@svelte-cli/core/js'; +import { addFromRawHtml } from '@svelte-cli/core/html'; export const adder = defineAdderConfig({ metadata: { @@ -66,20 +67,29 @@ export const adder = defineAdderConfig({ // Common to all Auth options { name: ({ typescript }) => `./src/hooks.server.${typescript ? 'ts' : 'js'}`, - contentType: 'text', + contentType: 'script', condition: ({ options }) => options.auth.length > 0, - content: ({ options, typescript }) => { + content: ({ ast, options, typescript }) => { const isTs = typescript; const { demo: isDemo } = options; - return dedent` - import { createServerClient } from '@supabase/ssr' - ${!isTs && isDemo ? `import { redirect } from '@sveltejs/kit'` : ''} - ${isTs && isDemo ? `import { type Handle, redirect } from '@sveltejs/kit'` : ''} - ${isTs && !isDemo ? `import type { Handle } from '@sveltejs/kit'` : ''} - import { sequence } from '@sveltejs/kit/hooks' - import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public' + imports.addNamed(ast, '@supabase/ssr', { createServerClient: 'createServerClient' }); + + if (!isTs && isDemo) imports.addNamed(ast, '@sveltejs/kit', { redirect: 'redirect' }); + if (isTs && isDemo) { + imports.addNamed(ast, '@sveltejs/kit', { redirect: 'redirect' }); + } + if (isTs) imports.addNamed(ast, '@sveltejs/kit', { Handle: 'Handle' }, true); + + imports.addNamed(ast, '@sveltejs/kit/hooks', { sequence: 'sequence' }); + imports.addNamed(ast, '$env/static/public', { + PUBLIC_SUPABASE_URL: 'PUBLIC_SUPABASE_URL', + PUBLIC_SUPABASE_ANON_KEY: 'PUBLIC_SUPABASE_ANON_KEY' + }); + common.addFromString( + ast, + ` const supabase${isTs ? ': Handle' : ''} = async ({ event, resolve }) => { event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { cookies: { @@ -141,7 +151,8 @@ export const adder = defineAdderConfig({ } export const handle${isTs ? ': Handle' : ''} = sequence(supabase, authGuard) - `; + ` + ); } }, { @@ -211,7 +222,7 @@ export const adder = defineAdderConfig({ common.addFromString( ast, - dedent` + ` export const load${typescript ? ': LayoutLoad' : ''} = async ({ data, depends, fetch }) => { depends('supabase:auth') @@ -254,7 +265,7 @@ export const adder = defineAdderConfig({ common.addFromString( ast, - dedent` + ` export const load${typescript ? ': LayoutServerLoad' : ''} = async ({ locals: { session }, cookies }) => { return { session, @@ -267,31 +278,31 @@ export const adder = defineAdderConfig({ }, { name: ({ kit }) => `${kit?.routesDirectory}/+layout.svelte`, - contentType: 'text', + contentType: 'svelte', condition: ({ options }) => options.auth.length > 0, - content: ({ typescript }) => { - return dedent` - - import { invalidate } from '$app/navigation'; - import { onMount } from 'svelte'; + content: ({ jsAst, htmlAst }) => { + imports.addNamed(jsAst, '$app/navigation', { invalidate: 'invalidate' }); + imports.addNamed(jsAst, 'svelte', { onMount: 'onMount' }); - /** @type {{data: any}} */ - let { children, data } = $props(); - let { session, supabase } = $derived(data); - - onMount(() => { - const { data } = supabase.auth.onAuthStateChange((_, newSession) => { - if (newSession?.expires_at !== session?.expires_at) { - invalidate('supabase:auth'); - } - }); + common.addFromString( + jsAst, + ` + let { children, data } = $props(); + let { session, supabase } = $derived(data); - return () => data.subscription.unsubscribe(); + onMount(() => { + const { data } = supabase.auth.onAuthStateChange((_, newSession) => { + if (newSession?.expires_at !== session?.expires_at) { + invalidate('supabase:auth'); + } }); - - {@render children?.()} - `; + return () => data.subscription.unsubscribe(); + }); + ` + ); + + addFromRawHtml(htmlAst.childNodes, '{@render children?.()}'); } }, { @@ -741,33 +752,39 @@ export const adder = defineAdderConfig({ // Demo routes when user has selected Basic Auth and/or Magic Link { name: ({ kit }) => `${kit?.routesDirectory}/+page.svelte`, - contentType: 'text', + contentType: 'svelte', condition: ({ options }) => options.demo, - content: ({ typescript }) => { - return dedent` - - import { page } from "$app/stores"; + content: ({ jsAst, htmlAst }) => { + imports.addNamed(jsAst, '$app/stores', { page: 'page' }); + common.addFromString( + jsAst, + ` async function logout() { const { error } = await $page.data.supabase.auth.signOut(); if (error) { console.error(error); } }; - + ` + ); -

    Welcome to SvelteKit with Supabase

    -
    - {#if $page.data.user} - Logout - {/if} -
    -						User: {JSON.stringify($page.data.user, null, 2)}
    -					
    - `; + addFromRawHtml( + htmlAst.childNodes, + ` +

    Welcome to SvelteKit with Supabase

    + + {#if $page.data.user} + Logout + {/if} +
    +							User: {JSON.stringify($page.data.user, null, 2)}
    +						
    + ` + ); } }, { From 572223591bb5e5078a1d7a5cb4b31709f5a9d9cf Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Thu, 3 Oct 2024 14:56:08 +0200 Subject: [PATCH 47/76] make `+hooks.server.ts` more robust --- packages/adders/common.ts | 223 ++++++++++++++++++++++- packages/adders/lucia/config/adder.ts | 188 +------------------ packages/adders/supabase/config/adder.ts | 138 +++++++------- 3 files changed, 293 insertions(+), 256 deletions(-) diff --git a/packages/adders/common.ts b/packages/adders/common.ts index d54dcdb8..9c00b10b 100644 --- a/packages/adders/common.ts +++ b/packages/adders/common.ts @@ -1,5 +1,5 @@ -import { imports, exports, common } from '@svelte-cli/core/js'; -import { Walker, type AstTypes, type ScriptFileEditor } from '@svelte-cli/core'; +import { imports, exports, common, variables, functions } from '@svelte-cli/core/js'; +import { Walker, type AstKinds, type AstTypes, type ScriptFileEditor } from '@svelte-cli/core'; import type { Question } from '@svelte-cli/core/internal'; export function addEslintConfigPrettier({ ast }: ScriptFileEditor>) { @@ -127,3 +127,222 @@ export function hasTypeProp( node.type === 'TSPropertySignature' && node.key.type === 'Identifier' && node.key.name === name ); } + +export function addHooksHandle( + ast: AstTypes.Program, + typescript: boolean, + newHandleName: string, + handleContent: string, + forceSeperateHandle: boolean = false +) { + if (typescript) { + imports.addNamed(ast, '@sveltejs/kit', { Handle: 'Handle' }, true); + } + + let isSpecifier: boolean = false; + let handleName = 'handle'; + let exportDecl: AstTypes.ExportNamedDeclaration | undefined; + let originalHandleDecl: AstKinds.DeclarationKind | undefined; + + // We'll first visit all of the named exports and grab their references if they export `handle`. + // This will grab export references for: + // `export { handle }` & `export { foo as handle }` + // `export const handle = ...`, & `export function handle() {...}` + // prettier-ignore + Walker.walk(ast as AstTypes.ASTNode, {}, { + ExportNamedDeclaration(node) { + let maybeHandleDecl: AstKinds.DeclarationKind | undefined; + + // `export { handle }` & `export { foo as handle }` + const handleSpecifier = node.specifiers?.find((s) => s.exported.name === 'handle'); + if (handleSpecifier) { + isSpecifier = true; + // we'll search for the local name in case it's aliased (e.g. `export { foo as handle }`) + handleName = handleSpecifier.local?.name ?? handleSpecifier.exported.name; + + // find the definition + const handleFunc = ast.body.find((n) => isFunctionDeclaration(n, handleName)); + const handleVar = ast.body.find((n) => isVariableDeclaration(n, handleName)); + + maybeHandleDecl = handleFunc ?? handleVar; + } + + maybeHandleDecl ??= node.declaration ?? undefined; + + // `export const handle` + if (maybeHandleDecl && isVariableDeclaration(maybeHandleDecl, handleName)) { + exportDecl = node; + originalHandleDecl = maybeHandleDecl; + } + + // `export function handle` + if (maybeHandleDecl && isFunctionDeclaration(maybeHandleDecl, handleName)) { + exportDecl = node; + originalHandleDecl = maybeHandleDecl; + } + }, + }); + + const newHandle = common.expressionFromString(handleContent); + if (common.hasNode(ast, newHandle)) return; + + // This is the straightforward case. If there's no existing `handle`, we'll just add one + // with the new handle's definition and exit + if (!originalHandleDecl || !exportDecl) { + // handle declaration doesn't exist, so we'll just create it with the hook + const newDecl = variables.declaration(ast, 'const', handleName, newHandle); + if (typescript) { + const declarator = newDecl.declarations[0] as AstTypes.VariableDeclarator; + variables.typeAnnotateDeclarator(declarator, 'Handle'); + } + + if (!forceSeperateHandle) exports.namedExport(ast, handleName, newDecl); + else { + const newDecl = variables.declaration(ast, 'const', newHandleName, newHandle); + if (typescript) { + const declarator = newDecl.declarations[0] as AstTypes.VariableDeclarator; + variables.typeAnnotateDeclarator(declarator, 'Handle'); + } + ast.body.push(newDecl); + + const handleDecl = variables.declaration( + ast, + 'const', + handleName, + common.expressionFromString(newHandleName) + ); + exports.namedExport(ast, handleName, handleDecl); + } + + return; + } + + // create the new handle + const newDecl = variables.declaration(ast, 'const', newHandleName, newHandle); + if (typescript) { + const declarator = newDecl.declarations[0] as AstTypes.VariableDeclarator; + variables.typeAnnotateDeclarator(declarator, 'Handle'); + } + + // check if `handle` is using a sequence + let sequence: AstTypes.CallExpression | undefined; + if (originalHandleDecl.type === 'VariableDeclaration') { + const handle = originalHandleDecl.declarations.find( + (d) => d.type === 'VariableDeclarator' && usingSequence(d, handleName) + ) as AstTypes.VariableDeclarator | undefined; + + sequence = handle?.init as AstTypes.CallExpression; + } + + // If `handle` is already using a `sequence`, then we'll just create the new handle and + // append the new handle name to the args of `sequence` + // e.g. `export const handle = sequence(some, other, handles, newHandle);` + if (sequence) { + const hasNewArg = sequence.arguments.some( + (arg) => arg.type === 'Identifier' && arg.name === newHandleName + ); + if (!hasNewArg) { + sequence.arguments.push(variables.identifier(newHandleName)); + } + + // removes the declarations so we can append them in the correct order + ast.body = ast.body.filter( + (n) => n !== originalHandleDecl && n !== exportDecl && n !== newDecl + ); + if (isSpecifier) { + // if export specifiers are being used (e.g. `export { handle }`), then we'll want + // need to also append original handle declaration as it's not part of the export declaration + ast.body.push(newDecl, originalHandleDecl, exportDecl); + } else { + ast.body.push(newDecl, exportDecl); + } + + return; + } + + // At this point, the existing `handle` doesn't call `sequence`, so we'll need to rename the original + // `handle` and create a new `handle` that uses `sequence` + // e.g. `const handle = sequence(originalHandle, newHandle);` + const NEW_HANDLE_NAME = 'originalHandle'; + const sequenceCall = functions.callByIdentifier('sequence', [NEW_HANDLE_NAME, newHandleName]); + const newHandleDecl = variables.declaration(ast, 'const', handleName, sequenceCall); + + imports.addNamed(ast, '@sveltejs/kit/hooks', { sequence: 'sequence' }); + + let renameRequired = false; + // rename `export const handle` + if (originalHandleDecl && isVariableDeclaration(originalHandleDecl, handleName)) { + const handle = getVariableDeclarator(originalHandleDecl, handleName); + if (handle && handle.id.type === 'Identifier' && handle.init?.type !== 'Identifier') { + renameRequired = true; + handle.id.name = NEW_HANDLE_NAME; + } + } + // rename `export function handle` + if (originalHandleDecl && isFunctionDeclaration(originalHandleDecl, handleName)) { + renameRequired = true; + originalHandleDecl.id!.name = NEW_HANDLE_NAME; + } + + // removes all declarations so that we can re-append them in the correct order + ast.body = ast.body.filter((n) => n !== originalHandleDecl && n !== exportDecl && n !== newDecl); + + if (isSpecifier) { + ast.body.push(originalHandleDecl, newDecl, newHandleDecl, exportDecl); + } + + if (exportDecl.declaration && renameRequired) { + // when we re-append the declarations, we only want to add the declaration + // of the (now renamed) original `handle` _without_ the `export` keyword: + // e.g. `const originalHandle = ...;` + ast.body.push(exportDecl.declaration, newDecl); + // `export const handle = sequence(originalHandle, newHandle);` + exports.namedExport(ast, handleName, newHandleDecl); + } else if (exportDecl.declaration && isVariableDeclaration(originalHandleDecl, handleName)) { + // if the previous value of `export const handle = ...` was an identifier + // there is no need to rename the handle, we just need to add it to the sequence + const variableDeclarator = getVariableDeclarator(originalHandleDecl, handleName); + const sequenceCall = functions.callByIdentifier('sequence', [ + (variableDeclarator?.init as AstTypes.Identifier).name, + newHandleName + ]); + const newHandleDecl = variables.declaration(ast, 'const', handleName, sequenceCall); + ast.body.push(newDecl); + exports.namedExport(ast, handleName, newHandleDecl); + } +} + +function usingSequence(node: AstTypes.VariableDeclarator, handleName: string) { + return ( + node.id.type === 'Identifier' && + node.id.name === handleName && + node.init?.type === 'CallExpression' && + node.init.callee.type === 'Identifier' && + node.init.callee.name === 'sequence' + ); +} + +function isVariableDeclaration( + node: AstTypes.ASTNode, + variableName: string +): node is AstTypes.VariableDeclaration { + return ( + node.type === 'VariableDeclaration' && getVariableDeclarator(node, variableName) !== undefined + ); +} + +function getVariableDeclarator( + node: AstTypes.VariableDeclaration, + handleName: string +): AstTypes.VariableDeclarator | undefined { + return node.declarations.find( + (d) => d.type === 'VariableDeclarator' && d.id.type === 'Identifier' && d.id.name === handleName + ) as AstTypes.VariableDeclarator | undefined; +} + +function isFunctionDeclaration( + node: AstTypes.ASTNode, + funcName: string +): node is AstTypes.FunctionDeclaration { + return node.type === 'FunctionDeclaration' && node.id?.name === funcName; +} diff --git a/packages/adders/lucia/config/adder.ts b/packages/adders/lucia/config/adder.ts index 48bbb212..3f2ef83b 100644 --- a/packages/adders/lucia/config/adder.ts +++ b/packages/adders/lucia/config/adder.ts @@ -2,8 +2,8 @@ import { options } from './options.ts'; import { colors, dedent, defineAdderConfig, log, Walker } from '@svelte-cli/core'; import { common, exports, imports, variables, object, functions } from '@svelte-cli/core/js'; // eslint-disable-next-line no-duplicate-imports -import type { AstKinds, AstTypes } from '@svelte-cli/core/js'; -import { getOrCreateAppInterface, hasTypeProp } from '../../common.ts'; +import type { AstTypes } from '@svelte-cli/core/js'; +import { addHooksHandle, getOrCreateAppInterface, hasTypeProp } from '../../common.ts'; const LUCIA_ADAPTER = { mysql: 'DrizzleMySQLAdapter', @@ -262,154 +262,7 @@ export const adder = defineAdderConfig({ content: ({ ast, typescript }) => { imports.addNamed(ast, '$lib/server/auth.js', { lucia: 'lucia' }); - if (typescript) { - imports.addNamed(ast, '@sveltejs/kit', { Handle: 'Handle' }, true); - } - - let isSpecifier: boolean = false; - let handleName = 'handle'; - let exportDecl: AstTypes.ExportNamedDeclaration | undefined; - let originalHandleDecl: AstKinds.DeclarationKind | undefined; - - // We'll first visit all of the named exports and grab their references if they export `handle`. - // This will grab export references for: - // `export { handle }` & `export { foo as handle }` - // `export const handle = ...`, & `export function handle() {...}` - // prettier-ignore - Walker.walk(ast as AstTypes.ASTNode, {}, { - ExportNamedDeclaration(node) { - let maybeHandleDecl: AstKinds.DeclarationKind | undefined; - - // `export { handle }` & `export { foo as handle }` - const handleSpecifier = node.specifiers?.find((s) => s.exported.name === 'handle'); - if (handleSpecifier) { - isSpecifier = true; - // we'll search for the local name in case it's aliased (e.g. `export { foo as handle }`) - handleName = handleSpecifier.local?.name ?? handleSpecifier.exported.name; - - // find the definition - const handleFunc = ast.body.find((n) => isFunctionDeclaration(n, handleName)); - const handleVar = ast.body.find((n) => isVariableDeclaration(n, handleName)); - - maybeHandleDecl = handleFunc ?? handleVar; - } - - maybeHandleDecl ??= node.declaration ?? undefined; - - // `export const handle` - if (maybeHandleDecl && isVariableDeclaration(maybeHandleDecl, handleName)) { - exportDecl = node; - originalHandleDecl = maybeHandleDecl; - } - - // `export function handle` - if (maybeHandleDecl && isFunctionDeclaration(maybeHandleDecl, handleName)) { - exportDecl = node; - originalHandleDecl = maybeHandleDecl; - } - }, - }); - - const authHandle = common.expressionFromString(getAuthHandleContent()); - if (common.hasNode(ast, authHandle)) return; - - // This is the straightforward case. If there's no existing `handle`, we'll just add one - // with the `auth` handle's definition and exit - if (!originalHandleDecl || !exportDecl) { - // handle declaration doesn't exist, so we'll just create it with the hook - const authDecl = variables.declaration(ast, 'const', handleName, authHandle); - if (typescript) { - const declarator = authDecl.declarations[0] as AstTypes.VariableDeclarator; - variables.typeAnnotateDeclarator(declarator, 'Handle'); - } - - exports.namedExport(ast, handleName, authDecl); - - return; - } - - // create the `auth` handle - const authName = 'auth'; - const authDecl = variables.declaration(ast, 'const', authName, authHandle); - if (typescript) { - const declarator = authDecl.declarations[0] as AstTypes.VariableDeclarator; - variables.typeAnnotateDeclarator(declarator, 'Handle'); - } - - // check if `handle` is using a sequence - let sequence: AstTypes.CallExpression | undefined; - if (originalHandleDecl.type === 'VariableDeclaration') { - const handle = originalHandleDecl.declarations.find( - (d) => d.type === 'VariableDeclarator' && usingSequence(d, handleName) - ) as AstTypes.VariableDeclarator | undefined; - - sequence = handle?.init as AstTypes.CallExpression; - } - - // If `handle` is already using a `sequence`, then we'll just create the `auth` handle and - // append `auth` to the args of `sequence` - // e.g. `export const handle = sequence(some, other, handles, auth);` - if (sequence) { - const hasAuthArg = sequence.arguments.some( - (arg) => arg.type === 'Identifier' && arg.name === authName - ); - if (!hasAuthArg) { - sequence.arguments.push(variables.identifier(authName)); - } - - // removes the declarations so we can append them in the correct order - ast.body = ast.body.filter( - (n) => n !== originalHandleDecl && n !== exportDecl && n !== authDecl - ); - if (isSpecifier) { - // if export specifiers are being used (e.g. `export { handle }`), then we'll want - // need to also append original handle declaration as it's not part of the export declaration - ast.body.push(authDecl, originalHandleDecl, exportDecl); - } else { - ast.body.push(authDecl, exportDecl); - } - - return; - } - - // At this point, the existing `handle` doesn't call `sequence`, so we'll need to rename the original - // `handle` and create a new `handle` that uses `sequence` - // e.g. `const handle = sequence(originalHandle, auth);` - const NEW_HANDLE_NAME = 'originalHandle'; - const sequenceCall = functions.callByIdentifier('sequence', [NEW_HANDLE_NAME, authName]); - const newHandleDecl = variables.declaration(ast, 'const', handleName, sequenceCall); - - imports.addNamed(ast, '@sveltejs/kit/hooks', { sequence: 'sequence' }); - - // rename `export const handle` - if (originalHandleDecl && isVariableDeclaration(originalHandleDecl, handleName)) { - const handle = getVariableDeclarator(originalHandleDecl, handleName); - if (handle && handle.id.type === 'Identifier') { - handle.id.name = NEW_HANDLE_NAME; - } - } - // rename `export function handle` - if (originalHandleDecl && isFunctionDeclaration(originalHandleDecl, handleName)) { - originalHandleDecl.id!.name = NEW_HANDLE_NAME; - } - - // removes all declarations so that we can re-append them in the correct order - ast.body = ast.body.filter( - (n) => n !== originalHandleDecl && n !== exportDecl && n !== authDecl - ); - - if (isSpecifier) { - ast.body.push(originalHandleDecl, authDecl, newHandleDecl, exportDecl); - } - - if (exportDecl.declaration) { - // when we re-append the declarations, we only want to add the declaration - // of the (now renamed) original `handle` _without_ the `export` keyword: - // e.g. `const originalHandle = ...;` - ast.body.push(exportDecl.declaration, authDecl); - // `export const handle = sequence(originalHandle, auth);` - exports.namedExport(ast, handleName, newHandleDecl); - } + addHooksHandle(ast, typescript, 'auth', getAuthHandleContent()); } }, // DEMO @@ -711,41 +564,6 @@ function createLuciaType(name: string): AstTypes.TSInterfaceBody['body'][number] }; } -function usingSequence(node: AstTypes.VariableDeclarator, handleName: string) { - return ( - node.id.type === 'Identifier' && - node.id.name === handleName && - node.init?.type === 'CallExpression' && - node.init.callee.type === 'Identifier' && - node.init.callee.name === 'sequence' - ); -} - -function isVariableDeclaration( - node: AstTypes.ASTNode, - variableName: string -): node is AstTypes.VariableDeclaration { - return ( - node.type === 'VariableDeclaration' && getVariableDeclarator(node, variableName) !== undefined - ); -} - -function getVariableDeclarator( - node: AstTypes.VariableDeclaration, - handleName: string -): AstTypes.VariableDeclarator | undefined { - return node.declarations.find( - (d) => d.type === 'VariableDeclarator' && d.id.type === 'Identifier' && d.id.name === handleName - ) as AstTypes.VariableDeclarator | undefined; -} - -function isFunctionDeclaration( - node: AstTypes.ASTNode, - funcName: string -): node is AstTypes.FunctionDeclaration { - return node.type === 'FunctionDeclaration' && node.id?.name === funcName; -} - function getAuthHandleContent() { return ` async ({ event, resolve }) => { diff --git a/packages/adders/supabase/config/adder.ts b/packages/adders/supabase/config/adder.ts index 8d3b297c..7a2ae305 100644 --- a/packages/adders/supabase/config/adder.ts +++ b/packages/adders/supabase/config/adder.ts @@ -6,7 +6,7 @@ import { type AstTypes } from '@svelte-cli/core'; import { options as availableOptions } from './options.ts'; -import { getOrCreateAppInterface, hasTypeProp } from '../../common.ts'; +import { addHooksHandle, getOrCreateAppInterface, hasTypeProp } from '../../common.ts'; import { common, imports } from '@svelte-cli/core/js'; import { addFromRawHtml } from '@svelte-cli/core/html'; @@ -79,80 +79,14 @@ export const adder = defineAdderConfig({ if (isTs && isDemo) { imports.addNamed(ast, '@sveltejs/kit', { redirect: 'redirect' }); } - if (isTs) imports.addNamed(ast, '@sveltejs/kit', { Handle: 'Handle' }, true); - imports.addNamed(ast, '@sveltejs/kit/hooks', { sequence: 'sequence' }); imports.addNamed(ast, '$env/static/public', { PUBLIC_SUPABASE_URL: 'PUBLIC_SUPABASE_URL', PUBLIC_SUPABASE_ANON_KEY: 'PUBLIC_SUPABASE_ANON_KEY' }); - common.addFromString( - ast, - ` - const supabase${isTs ? ': Handle' : ''} = async ({ event, resolve }) => { - event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { - cookies: { - getAll: () => event.cookies.getAll(), - setAll: (cookiesToSet) => { - cookiesToSet.forEach(({ name, value, options }) => { - event.cookies.set(name, value, { ...options, path: '/' }) - }) - }, - }, - }) - - event.locals.safeGetSession = async () => { - const { - data: { session }, - } = await event.locals.supabase.auth.getSession() - if (!session) { - return { session: null, user: null } - } - - const { - data: { user }, - error, - } = await event.locals.supabase.auth.getUser() - if (error) { - return { session: null, user: null } - } - - return { session, user } - } - - return resolve(event, { - filterSerializedResponseHeaders(name) { - return name === 'content-range' || name === 'x-supabase-api-version' - }, - }) - } - - const authGuard${isTs ? ': Handle' : ''} = async ({ event, resolve }) => { - const { session, user } = await event.locals.safeGetSession() - event.locals.session = session - event.locals.user = user - ${ - isDemo - ? ` - if (!event.locals.session && event.url.pathname.startsWith('/private')) { - redirect(303, '/auth') - } - - if (event.locals.session && event.url.pathname === '/auth') { - redirect(303, '/private') - } - ` - : ` - // Add authentication guards here - ` - } - return resolve(event) - } - - export const handle${isTs ? ': Handle' : ''} = sequence(supabase, authGuard) - ` - ); + addHooksHandle(ast, typescript, 'supabase', getSupabaseHandleContent(), true); + addHooksHandle(ast, typescript, 'authGuard', getAuthGuardHandleContent(isDemo)); } }, { @@ -1156,3 +1090,69 @@ function createUserType(name: string): AstTypes.TSPropertySignature { } }; } + +function getSupabaseHandleContent() { + return ` + async ({ event, resolve }) => { + event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { + cookies: { + getAll: () => event.cookies.getAll(), + setAll: (cookiesToSet) => { + cookiesToSet.forEach(({ name, value, options }) => { + event.cookies.set(name, value, { ...options, path: '/' }) + }) + }, + }, + }) + + event.locals.safeGetSession = async () => { + const { + data: { session }, + } = await event.locals.supabase.auth.getSession() + if (!session) { + return { session: null, user: null } + } + + const { + data: { user }, + error, + } = await event.locals.supabase.auth.getUser() + if (error) { + return { session: null, user: null } + } + + return { session, user } + } + + return resolve(event, { + filterSerializedResponseHeaders(name) { + return name === 'content-range' || name === 'x-supabase-api-version' + }, + }); + };`; +} + +function getAuthGuardHandleContent(isDemo: boolean) { + return ` + async ({ event, resolve }) => { + const { session, user } = await event.locals.safeGetSession() + event.locals.session = session + event.locals.user = user + ${ + isDemo + ? ` + if (!event.locals.session && event.url.pathname.startsWith('/private')) { + redirect(303, '/auth') + } + + if (event.locals.session && event.url.pathname === '/auth') { + redirect(303, '/private') + } + ` + : ` + // Add authentication guards here + ` + } + return resolve(event); + }`; +} From 0af1308b464057e7b8e48c6c0fe21c72c9b30917 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Thu, 3 Oct 2024 15:13:31 +0200 Subject: [PATCH 48/76] fix duplicated handle import --- packages/core/tests/js/imports/named-import/output.ts | 1 + packages/core/tests/js/imports/named-import/run.ts | 4 ++++ packages/core/tooling/js/imports.ts | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/core/tests/js/imports/named-import/output.ts b/packages/core/tests/js/imports/named-import/output.ts index 2df2c201..2ae493d7 100644 --- a/packages/core/tests/js/imports/named-import/output.ts +++ b/packages/core/tests/js/imports/named-import/output.ts @@ -1 +1,2 @@ +import { Handle } from "@sveltejs/kit"; import { namedOne } from "package"; \ No newline at end of file diff --git a/packages/core/tests/js/imports/named-import/run.ts b/packages/core/tests/js/imports/named-import/run.ts index e0548202..ad439355 100644 --- a/packages/core/tests/js/imports/named-import/run.ts +++ b/packages/core/tests/js/imports/named-import/run.ts @@ -3,4 +3,8 @@ import type { ScriptFileEditor } from '@svelte-cli/core'; export function run({ ast }: ScriptFileEditor): void { imports.addNamed(ast, 'package', { namedOne: 'namedOne' }, false); + + imports.addNamed(ast, '@sveltejs/kit', { Handle: 'Handle' }, false); + // adding the same import twice should not produce two imports + imports.addNamed(ast, '@sveltejs/kit', { Handle: 'Handle' }, false); } diff --git a/packages/core/tooling/js/imports.ts b/packages/core/tooling/js/imports.ts index 8a545fcf..c164095e 100644 --- a/packages/core/tooling/js/imports.ts +++ b/packages/core/tooling/js/imports.ts @@ -60,7 +60,7 @@ export function addNamed( // prettier-ignore Walker.walk(ast as AstTypes.ASTNode, {}, { ImportDeclaration(node) { - if (node.source.type === 'StringLiteral' && node.source.value === importFrom && node.specifiers) { + if ((node.source.type === 'StringLiteral' || node.source.type === 'Literal') && node.source.value === importFrom && node.specifiers) { importDecl = node; } }, From 21b16fce1035ed8b621df098efdbbb2fa8b67085 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Thu, 3 Oct 2024 15:17:19 +0200 Subject: [PATCH 49/76] add missing type --- packages/adders/common.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/adders/common.ts b/packages/adders/common.ts index 9c00b10b..9985b7a4 100644 --- a/packages/adders/common.ts +++ b/packages/adders/common.ts @@ -211,6 +211,10 @@ export function addHooksHandle( handleName, common.expressionFromString(newHandleName) ); + if (typescript) { + const declarator = handleDecl.declarations[0] as AstTypes.VariableDeclarator; + variables.typeAnnotateDeclarator(declarator, 'Handle'); + } exports.namedExport(ast, handleName, handleDecl); } @@ -307,6 +311,10 @@ export function addHooksHandle( newHandleName ]); const newHandleDecl = variables.declaration(ast, 'const', handleName, sequenceCall); + if (typescript) { + const declarator = newHandleDecl.declarations[0] as AstTypes.VariableDeclarator; + variables.typeAnnotateDeclarator(declarator, 'Handle'); + } ast.body.push(newDecl); exports.namedExport(ast, handleName, newHandleDecl); } From a5d4d6149483476269229873e0ab430151d9cad9 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sun, 6 Oct 2024 08:34:15 +0200 Subject: [PATCH 50/76] fix merge issues --- packages/cli/commands/add.ts | 9 +++++---- packages/cli/commands/check.ts | 2 +- packages/cli/commands/migrate.ts | 2 +- packages/cli/common.ts | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/cli/commands/add.ts b/packages/cli/commands/add.ts index 5c91abf5..8b9e4a10 100644 --- a/packages/cli/commands/add.ts +++ b/packages/cli/commands/add.ts @@ -5,7 +5,7 @@ import { exec } from 'tinyexec'; import { Command, Option } from 'commander'; import * as p from '@svelte-cli/clack-prompts'; import * as pkg from 'empathic/package'; -import { resolveCommand } from 'package-manager-detector'; +import { COMMANDS, constructCommand, resolveCommand } from 'package-manager-detector'; import pc from 'picocolors'; import { adderCategories, @@ -18,7 +18,9 @@ import { import { createOrUpdateFiles, createWorkspace, + detectPackageManager, installPackages, + TESTING, type Workspace } from '@svelte-cli/core/internal'; import type { @@ -29,7 +31,6 @@ import type { } from '@svelte-cli/core'; import * as common from '../common.js'; import { Directive, downloadPackage, getPackageJSON } from '../utils/fetch-packages.js'; -import { COMMANDS, constructCommand } from 'package-manager-detector'; const AddersSchema = v.array(v.string()); const AdderOptionFlagsSchema = v.object({ @@ -675,10 +676,10 @@ export async function runScripts( } try { - const pm = await common.guessPackageManager(cwd); + const pm = await detectPackageManager(workspace.cwd); const cmd = resolveCommand(pm, 'execute', script.args)!; await exec(cmd.command, cmd.args, { - nodeOptions: { cwd, stdio: TESTING ? 'pipe' : 'inherit' } + nodeOptions: { cwd: workspace.cwd, stdio: TESTING ? 'pipe' : 'inherit' } }); const executeCommand = COMMANDS[workspace.packageManager].execute; diff --git a/packages/cli/commands/check.ts b/packages/cli/commands/check.ts index 2a0a4a2c..205bc7ec 100644 --- a/packages/cli/commands/check.ts +++ b/packages/cli/commands/check.ts @@ -3,7 +3,7 @@ import pc from 'picocolors'; import * as resolve from 'empathic/resolve'; import { Command } from 'commander'; import { resolveCommand } from 'package-manager-detector/commands'; -import { getUserAgent } from '../common.ts'; +import { getUserAgent } from '@svelte-cli/core/internal'; export const check = new Command('check') .description('a CLI for checking your Svelte code') diff --git a/packages/cli/commands/migrate.ts b/packages/cli/commands/migrate.ts index 73232190..5699628d 100644 --- a/packages/cli/commands/migrate.ts +++ b/packages/cli/commands/migrate.ts @@ -1,7 +1,7 @@ import { execSync } from 'node:child_process'; import { Command } from 'commander'; import { resolveCommand } from 'package-manager-detector'; -import { getUserAgent } from '../common.ts'; +import { getUserAgent } from '@svelte-cli/core/internal'; export const migrate = new Command('migrate') .description('a CLI for migrating Svelte(Kit) codebases') diff --git a/packages/cli/common.ts b/packages/cli/common.ts index f84c9b42..48641eb6 100644 --- a/packages/cli/common.ts +++ b/packages/cli/common.ts @@ -6,7 +6,7 @@ import { detect, AGENTS, type AgentName } from 'package-manager-detector'; import { COMMANDS, constructCommand, resolveCommand } from 'package-manager-detector/commands'; import type { AdderWithoutExplicitArgs, Precondition } from '@svelte-cli/core'; import type { Argument, HelpConfiguration, Option } from 'commander'; -import { getUserAgent } from '@svelte-cli/core/internal'; +import { detectPackageManager, getUserAgent } from '@svelte-cli/core/internal'; export const helpConfig: HelpConfiguration = { argumentDescription: formatDescription, @@ -42,7 +42,7 @@ export async function runCommand(action: MaybePromise) { } export async function formatFiles(cwd: string, paths: string[]): Promise { - const pm = await guessPackageManager(cwd); + const pm = await detectPackageManager(cwd); const args = ['prettier', '--write', '--ignore-unknown', ...paths]; const cmd = resolveCommand(pm, 'execute-local', args)!; await exec(cmd.command, cmd.args, { From 40a073b1d5b08fc9a2454758d97eb62cd414928e Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sun, 6 Oct 2024 08:42:48 +0200 Subject: [PATCH 51/76] fix arg parsing --- packages/cli/commands/add.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cli/commands/add.ts b/packages/cli/commands/add.ts index 8b9e4a10..514185b0 100644 --- a/packages/cli/commands/add.ts +++ b/packages/cli/commands/add.ts @@ -35,7 +35,8 @@ import { Directive, downloadPackage, getPackageJSON } from '../utils/fetch-packa const AddersSchema = v.array(v.string()); const AdderOptionFlagsSchema = v.object({ tailwindcss: v.optional(v.array(v.string())), - drizzle: v.optional(v.array(v.string())) + drizzle: v.optional(v.array(v.string())), + supabase: v.optional(v.array(v.string())) }); const OptionsSchema = v.strictObject({ cwd: v.string(), From 033513187ff2568cb5c5d9f8abef63ad42f7f1f9 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sun, 6 Oct 2024 08:43:02 +0200 Subject: [PATCH 52/76] fix merge issue --- packages/cli/commands/add.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/cli/commands/add.ts b/packages/cli/commands/add.ts index 514185b0..e9bfd6c0 100644 --- a/packages/cli/commands/add.ts +++ b/packages/cli/commands/add.ts @@ -20,7 +20,6 @@ import { createWorkspace, detectPackageManager, installPackages, - TESTING, type Workspace } from '@svelte-cli/core/internal'; import type { @@ -680,7 +679,7 @@ export async function runScripts( const pm = await detectPackageManager(workspace.cwd); const cmd = resolveCommand(pm, 'execute', script.args)!; await exec(cmd.command, cmd.args, { - nodeOptions: { cwd: workspace.cwd, stdio: TESTING ? 'pipe' : 'inherit' } + nodeOptions: { cwd: workspace.cwd, stdio: script.stdio } }); const executeCommand = COMMANDS[workspace.packageManager].execute; From 426ae5131493b58962b7022f20a4bbdac418edb9 Mon Sep 17 00:00:00 2001 From: AdrianGonz97 <31664583+AdrianGonz97@users.noreply.github.com> Date: Sun, 6 Oct 2024 12:23:28 -0400 Subject: [PATCH 53/76] word --- packages/adders/common.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/adders/common.ts b/packages/adders/common.ts index 9985b7a4..e17f11b5 100644 --- a/packages/adders/common.ts +++ b/packages/adders/common.ts @@ -133,7 +133,7 @@ export function addHooksHandle( typescript: boolean, newHandleName: string, handleContent: string, - forceSeperateHandle: boolean = false + forceSeparateHandle: boolean = false ) { if (typescript) { imports.addNamed(ast, '@sveltejs/kit', { Handle: 'Handle' }, true); @@ -196,7 +196,7 @@ export function addHooksHandle( variables.typeAnnotateDeclarator(declarator, 'Handle'); } - if (!forceSeperateHandle) exports.namedExport(ast, handleName, newDecl); + if (!forceSeparateHandle) exports.namedExport(ast, handleName, newDecl); else { const newDecl = variables.declaration(ast, 'const', newHandleName, newHandle); if (typescript) { From 9d8a85fd54e7239d4e80db473214e6495206ffa3 Mon Sep 17 00:00:00 2001 From: AdrianGonz97 <31664583+AdrianGonz97@users.noreply.github.com> Date: Sun, 6 Oct 2024 12:24:07 -0400 Subject: [PATCH 54/76] simplify script execution --- packages/cli/commands/add.ts | 52 +++++++++--------------------------- 1 file changed, 13 insertions(+), 39 deletions(-) diff --git a/packages/cli/commands/add.ts b/packages/cli/commands/add.ts index e9bfd6c0..aa66205d 100644 --- a/packages/cli/commands/add.ts +++ b/packages/cli/commands/add.ts @@ -5,7 +5,7 @@ import { exec } from 'tinyexec'; import { Command, Option } from 'commander'; import * as p from '@svelte-cli/clack-prompts'; import * as pkg from 'empathic/package'; -import { COMMANDS, constructCommand, resolveCommand } from 'package-manager-detector'; +import { resolveCommand } from 'package-manager-detector'; import pc from 'picocolors'; import { adderCategories, @@ -18,16 +18,10 @@ import { import { createOrUpdateFiles, createWorkspace, - detectPackageManager, installPackages, type Workspace } from '@svelte-cli/core/internal'; -import type { - AdderWithoutExplicitArgs, - OptionDefinition, - OptionValues, - Scripts -} from '@svelte-cli/core'; +import type { AdderWithoutExplicitArgs, OptionValues, Scripts } from '@svelte-cli/core'; import * as common from '../common.js'; import { Directive, downloadPackage, getPackageJSON } from '../utils/fetch-packages.js'; @@ -551,6 +545,7 @@ export async function installAdders({ const filesToFormat = new Set(); for (const { config } of details) { const adderId = config.metadata.id; + // TODO: make this sync const workspace = await createWorkspace(cwd); workspace.options = official[adderId] ?? community[adderId]; @@ -559,8 +554,11 @@ export async function installAdders({ const pkgPath = installPackages(config, workspace); filesToFormat.add(pkgPath); const changedFiles = createOrUpdateFiles(config.files, workspace); - if (config.scripts) await runScripts(config.scripts, workspace); changedFiles.forEach((file) => filesToFormat.add(file)); + + if (config.scripts && config.scripts.length > 0) { + await runScripts(config.metadata.name, config.scripts, workspace); + } } return Array.from(filesToFormat); @@ -655,38 +653,19 @@ function getPadding(lines: string[]) { return Math.max(...lengths); } -export async function runScripts( - scripts: Array>, - workspace: Workspace -) { - if (scripts.length < 1) return; - - const loadingSpinner = p.spinner(); - const runningScriptsText = 'Running scripts...'; - loadingSpinner.start(runningScriptsText); +async function runScripts(adder: string, scripts: Array>, workspace: Workspace) { + p.log.step(`Running external command ${pc.gray(`(${adder})`)}`); for (const script of scripts) { - if (script.condition && !script.condition(workspace)) { + if (script.condition?.(workspace) === false) { continue; } - if (script.stdio == 'inherit') { - // stop spinner as it will interfere with the script output - loadingSpinner.stop(runningScriptsText); - } - try { - const pm = await detectPackageManager(workspace.cwd); - const cmd = resolveCommand(pm, 'execute', script.args)!; - await exec(cmd.command, cmd.args, { - nodeOptions: { cwd: workspace.cwd, stdio: script.stdio } - }); - - const executeCommand = COMMANDS[workspace.packageManager].execute; - const { command, args } = constructCommand(executeCommand, script.args)!; + const { command, args } = resolveCommand(workspace.packageManager, 'execute', script.args)!; // adding --yes as the first parameter helps avoiding the "Need to install the following packages:" message - if (workspace.packageManager == 'npm') args.unshift('--yes'); + if (workspace.packageManager === 'npm') args.unshift('--yes'); await exec(command, args, { nodeOptions: { cwd: workspace.cwd, stdio: script.stdio } @@ -695,12 +674,7 @@ export async function runScripts( const typedError = error as Error; throw new Error(`Failed to execute scripts '${script.description}': ` + typedError.message); } - - if (script.stdio == 'inherit') { - // resume spinner as it will no longer interfere with the script output - loadingSpinner.start(runningScriptsText); - } } - loadingSpinner.stop('Successfully executed scripts'); + p.log.success(`Finished running ${adder}`); } From 65b0bde1d14e20e3754736ab5c27899696c28768 Mon Sep 17 00:00:00 2001 From: AdrianGonz97 <31664583+AdrianGonz97@users.noreply.github.com> Date: Sun, 6 Oct 2024 12:24:10 -0400 Subject: [PATCH 55/76] dedent --- packages/adders/supabase/config/adder.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/adders/supabase/config/adder.ts b/packages/adders/supabase/config/adder.ts index 7a2ae305..bc76c3d8 100644 --- a/packages/adders/supabase/config/adder.ts +++ b/packages/adders/supabase/config/adder.ts @@ -1092,7 +1092,7 @@ function createUserType(name: string): AstTypes.TSPropertySignature { } function getSupabaseHandleContent() { - return ` + return dedent` async ({ event, resolve }) => { event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { cookies: { @@ -1133,7 +1133,7 @@ function getSupabaseHandleContent() { } function getAuthGuardHandleContent(isDemo: boolean) { - return ` + return dedent` async ({ event, resolve }) => { const { session, user } = await event.locals.safeGetSession() event.locals.session = session From e0a0e21728d72659db02f2ff1ca3ab7e8c667f0c Mon Sep 17 00:00:00 2001 From: AdrianGonz97 <31664583+AdrianGonz97@users.noreply.github.com> Date: Sun, 6 Oct 2024 15:49:19 -0400 Subject: [PATCH 56/76] more tweaks --- packages/adders/supabase/config/adder.ts | 2 +- packages/cli/commands/add.ts | 56 ++++++++++++------------ 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/packages/adders/supabase/config/adder.ts b/packages/adders/supabase/config/adder.ts index bc76c3d8..2b7ebb21 100644 --- a/packages/adders/supabase/config/adder.ts +++ b/packages/adders/supabase/config/adder.ts @@ -705,7 +705,7 @@ export const adder = defineAdderConfig({ addFromRawHtml( htmlAst.childNodes, - ` + dedent`

    Welcome to SvelteKit with Supabase

    • Login
    • diff --git a/packages/cli/commands/add.ts b/packages/cli/commands/add.ts index aa66205d..a29d2298 100644 --- a/packages/cli/commands/add.ts +++ b/packages/cli/commands/add.ts @@ -477,8 +477,8 @@ export async function runAddCommand(options: Options, adders: string[]): Promise // print next steps const nextStepsMsg = selectedAdders .filter(({ adder }) => adder.config.nextSteps) - .map(({ adder }) => adder.config) - .map((config) => { + .map(({ adder }) => { + const config = adder.config; const metadata = config.metadata; let adderMessage = ''; if (selectedAdders.length > 1) { @@ -564,6 +564,32 @@ export async function installAdders({ return Array.from(filesToFormat); } +async function runScripts(adder: string, scripts: Array>, workspace: Workspace) { + p.log.step(`Running external command ${pc.gray(`(${adder})`)}`); + + for (const script of scripts) { + if (script.condition?.(workspace) === false) { + continue; + } + + try { + const { command, args } = resolveCommand(workspace.packageManager, 'execute', script.args)!; + + // adding --yes as the first parameter helps avoiding the "Need to install the following packages:" message + if (workspace.packageManager === 'npm') args.unshift('--yes'); + + await exec(command, args, { + nodeOptions: { cwd: workspace.cwd, stdio: script.stdio } + }); + } catch (error) { + const typedError = error as Error; + throw new Error(`Failed to execute scripts '${script.description}': ` + typedError.message); + } + } + + p.log.success(`Finished running ${adder}`); +} + /** * Dedupes and transforms aliases into their respective adder id */ @@ -652,29 +678,3 @@ function getPadding(lines: string[]) { const lengths = lines.map((s) => s.length); return Math.max(...lengths); } - -async function runScripts(adder: string, scripts: Array>, workspace: Workspace) { - p.log.step(`Running external command ${pc.gray(`(${adder})`)}`); - - for (const script of scripts) { - if (script.condition?.(workspace) === false) { - continue; - } - - try { - const { command, args } = resolveCommand(workspace.packageManager, 'execute', script.args)!; - - // adding --yes as the first parameter helps avoiding the "Need to install the following packages:" message - if (workspace.packageManager === 'npm') args.unshift('--yes'); - - await exec(command, args, { - nodeOptions: { cwd: workspace.cwd, stdio: script.stdio } - }); - } catch (error) { - const typedError = error as Error; - throw new Error(`Failed to execute scripts '${script.description}': ` + typedError.message); - } - } - - p.log.success(`Finished running ${adder}`); -} From e4b863c75650d08cebef7bb97f7d9f0438b2dad0 Mon Sep 17 00:00:00 2001 From: AdrianGonz97 <31664583+AdrianGonz97@users.noreply.github.com> Date: Sun, 6 Oct 2024 17:45:07 -0400 Subject: [PATCH 57/76] simplify --- packages/adders/supabase/config/adder.ts | 10 ++-------- packages/core/adder/config.ts | 3 --- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/packages/adders/supabase/config/adder.ts b/packages/adders/supabase/config/adder.ts index 2b7ebb21..a52c484b 100644 --- a/packages/adders/supabase/config/adder.ts +++ b/packages/adders/supabase/config/adder.ts @@ -875,14 +875,8 @@ export const adder = defineAdderConfig({ } } ], - nextSteps: ({ options, packageManager, workspace }) => { - let command: string; - if (!packageManager || packageManager === 'npm') { - command = 'npx'; - } else { - command = packageManager; - } - + nextSteps: ({ options, workspace }) => { + const command = workspace.packageManager === 'npm' ? 'npx' : workspace.packageManager; const { auth, cli: isCli, helpers: isHelpers } = options; const isBasic = auth.includes('basic'); const isMagicLink = auth.includes('magicLink'); diff --git a/packages/core/adder/config.ts b/packages/core/adder/config.ts index 007b4b63..38ca353c 100644 --- a/packages/core/adder/config.ts +++ b/packages/core/adder/config.ts @@ -2,12 +2,10 @@ import type { OptionDefinition, OptionValues, Question } from './options.ts'; import type { FileType } from '../files/processors.ts'; import type { Workspace } from '../files/workspace.ts'; import type { Colors } from 'picocolors/types.ts'; -import type { AgentName } from 'package-manager-detector'; export type ConditionDefinition = ( Workspace: Workspace ) => boolean; -export type ConditionDefinitionWithoutExplicitArgs = ConditionDefinition>; export type WebsiteMetadata = { logo: string; @@ -56,7 +54,6 @@ export type AdderConfig = { cwd: string; colors: Colors; docs: string | undefined; - packageManager: AgentName; workspace: Workspace; }) => string[]; }; From 5ea4e0f02c3e2dea4ac35960f5c5fb67ca09ec98 Mon Sep 17 00:00:00 2001 From: AdrianGonz97 <31664583+AdrianGonz97@users.noreply.github.com> Date: Sun, 6 Oct 2024 17:47:06 -0400 Subject: [PATCH 58/76] no changesets yet --- .changeset/four-cheetahs-argue.md | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 .changeset/four-cheetahs-argue.md diff --git a/.changeset/four-cheetahs-argue.md b/.changeset/four-cheetahs-argue.md deleted file mode 100644 index 05d00735..00000000 --- a/.changeset/four-cheetahs-argue.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -'@svelte-add/testing-library': minor -'@svelte-add/config': minor -'@svelte-add/core': minor -'@svelte-add/adders': minor ---- - -feat: auto detect package manager -feat: add support for executing scripts (e.g., npx, pnpm dlx, etc) within an adder -feat: add support for multiselect options within an adder From fa54fc1295e4ebd102e27ebd4af34eb744a310d0 Mon Sep 17 00:00:00 2001 From: AdrianGonz97 <31664583+AdrianGonz97@users.noreply.github.com> Date: Sun, 6 Oct 2024 18:07:12 -0400 Subject: [PATCH 59/76] simplify --- packages/cli/commands/add.ts | 51 +++++++++++------------------ packages/core/tooling/js/imports.ts | 2 +- 2 files changed, 21 insertions(+), 32 deletions(-) diff --git a/packages/cli/commands/add.ts b/packages/cli/commands/add.ts index a29d2298..c09af09a 100644 --- a/packages/cli/commands/add.ts +++ b/packages/cli/commands/add.ts @@ -15,13 +15,8 @@ import { communityAdderIds, getCommunityAdder } from '@svelte-cli/adders'; -import { - createOrUpdateFiles, - createWorkspace, - installPackages, - type Workspace -} from '@svelte-cli/core/internal'; -import type { AdderWithoutExplicitArgs, OptionValues, Scripts } from '@svelte-cli/core'; +import { createOrUpdateFiles, createWorkspace, installPackages } from '@svelte-cli/core/internal'; +import type { AdderWithoutExplicitArgs, OptionValues } from '@svelte-cli/core'; import * as common from '../common.js'; import { Directive, downloadPackage, getPackageJSON } from '../utils/fetch-packages.js'; @@ -557,37 +552,31 @@ export async function installAdders({ changedFiles.forEach((file) => filesToFormat.add(file)); if (config.scripts && config.scripts.length > 0) { - await runScripts(config.metadata.name, config.scripts, workspace); - } - } - - return Array.from(filesToFormat); -} + const name = config.metadata.name; + p.log.step(`Running external command ${pc.gray(`(${name})`)}`); -async function runScripts(adder: string, scripts: Array>, workspace: Workspace) { - p.log.step(`Running external command ${pc.gray(`(${adder})`)}`); + for (const script of config.scripts) { + if (script.condition?.(workspace) === false) continue; - for (const script of scripts) { - if (script.condition?.(workspace) === false) { - continue; - } + const { command, args } = resolveCommand(workspace.packageManager, 'execute', script.args)!; + // adding --yes as the first parameter helps avoiding the "Need to install the following packages:" message + if (workspace.packageManager === 'npm') args.unshift('--yes'); - try { - const { command, args } = resolveCommand(workspace.packageManager, 'execute', script.args)!; - - // adding --yes as the first parameter helps avoiding the "Need to install the following packages:" message - if (workspace.packageManager === 'npm') args.unshift('--yes'); + try { + await exec(command, args, { nodeOptions: { cwd: workspace.cwd, stdio: script.stdio } }); + } catch (error) { + const typedError = error as Error; + throw new Error( + `Failed to execute scripts '${script.description}': ` + typedError.message + ); + } + } - await exec(command, args, { - nodeOptions: { cwd: workspace.cwd, stdio: script.stdio } - }); - } catch (error) { - const typedError = error as Error; - throw new Error(`Failed to execute scripts '${script.description}': ` + typedError.message); + p.log.success(`Finished running ${name}`); } } - p.log.success(`Finished running ${adder}`); + return Array.from(filesToFormat); } /** diff --git a/packages/core/tooling/js/imports.ts b/packages/core/tooling/js/imports.ts index c164095e..b6cd85a6 100644 --- a/packages/core/tooling/js/imports.ts +++ b/packages/core/tooling/js/imports.ts @@ -60,7 +60,7 @@ export function addNamed( // prettier-ignore Walker.walk(ast as AstTypes.ASTNode, {}, { ImportDeclaration(node) { - if ((node.source.type === 'StringLiteral' || node.source.type === 'Literal') && node.source.value === importFrom && node.specifiers) { + if (node.source.value === importFrom && node.specifiers) { importDecl = node; } }, From fd794d3cd5274ad87f8b41e420753d97830f3df8 Mon Sep 17 00:00:00 2001 From: AdrianGonz97 <31664583+AdrianGonz97@users.noreply.github.com> Date: Sun, 6 Oct 2024 18:10:47 -0400 Subject: [PATCH 60/76] update pm detector --- packages/core/package.json | 2 +- pnpm-lock.yaml | 11 +++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 9ce555d9..51a0f127 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -39,7 +39,7 @@ "@svelte-cli/clack-prompts": "workspace:*", "decircular": "^1.0.0", "dedent": "^1.5.3", - "package-manager-detector": "^0.2.0", + "package-manager-detector": "^0.2.1", "empathic": "^1.0.0", "picocolors": "^1.1.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 697ec83b..10fd3a8c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -213,8 +213,8 @@ importers: specifier: ^1.0.0 version: 1.0.0 package-manager-detector: - specifier: ^0.2.0 - version: 0.2.0 + specifier: ^0.2.1 + version: 0.2.1 picocolors: specifier: ^1.1.0 version: 1.1.0 @@ -1612,9 +1612,6 @@ packages: package-json-from-dist@1.0.0: resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} - package-manager-detector@0.2.0: - resolution: {integrity: sha512-E385OSk9qDcXhcM9LNSe4sdhx8a9mAPrZ4sMLW+tmxl5ZuGtPUcdFu+MPP2jbgiWAZ6Pfe5soGFMd+0Db5Vrog==} - package-manager-detector@0.2.1: resolution: {integrity: sha512-/hVW2fZvAdEas+wyKh0SnlZ2mx0NIa1+j11YaQkogEJkcMErbwchHCuo8z7lEtajZJQZ6rgZNVTWMVVd71Bjng==} @@ -2256,7 +2253,7 @@ snapshots: mri: 1.2.0 outdent: 0.5.0 p-limit: 2.3.0 - package-manager-detector: 0.2.0 + package-manager-detector: 0.2.1 picocolors: 1.1.0 resolve-from: 5.0.0 semver: 7.6.3 @@ -3586,8 +3583,6 @@ snapshots: package-json-from-dist@1.0.0: {} - package-manager-detector@0.2.0: {} - package-manager-detector@0.2.1: {} parent-module@1.0.1: From d463b284b3664045739716572fcfc44453eb8ff7 Mon Sep 17 00:00:00 2001 From: AdrianGonz97 <31664583+AdrianGonz97@users.noreply.github.com> Date: Sun, 6 Oct 2024 18:46:38 -0400 Subject: [PATCH 61/76] simplify app namespace --- packages/adders/common.ts | 22 ++++++++-------------- packages/adders/lucia/config/adder.ts | 4 ++-- packages/adders/supabase/config/adder.ts | 6 +++--- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/packages/adders/common.ts b/packages/adders/common.ts index e17f11b5..d48635a0 100644 --- a/packages/adders/common.ts +++ b/packages/adders/common.ts @@ -58,7 +58,7 @@ export function addEslintConfigPrettier({ ast }: ScriptFileEditor n.type === 'TSModuleDeclaration') .find((m) => m.global && m.declare); - if (globalDecl && globalDecl?.body?.type !== 'TSModuleBlock') { - throw new Error('Unexpected body type of `declare global` in `src/app.d.ts`'); - } - if (!globalDecl) { - const decl = common.statementFromString(` - declare global {}`) as AstTypes.TSModuleDeclaration; - ast.body.push(decl); - globalDecl = decl; + globalDecl = common.statementFromString('declare global {}') as AstTypes.TSModuleDeclaration; + ast.body.push(globalDecl); } - if (!globalDecl || !globalDecl.body || !globalDecl.body.body) { - throw new Error('Failed processing global declaration'); + if (globalDecl.body?.type !== 'TSModuleBlock') { + throw new Error('Unexpected body type of `declare global` in `src/app.d.ts`'); } let app: AstTypes.TSModuleDeclaration | undefined; @@ -100,8 +94,8 @@ export function getOrCreateAppInterface( }); if (!app) { - app ??= common.statementFromString('namespace App {}') as AstTypes.TSModuleDeclaration; - (globalDecl.body as AstTypes.TSModuleBlock).body.push(app); + app = common.statementFromString('namespace App {}') as AstTypes.TSModuleDeclaration; + globalDecl.body.body.push(app); } if (app.body?.type !== 'TSModuleBlock') { @@ -109,7 +103,7 @@ export function getOrCreateAppInterface( } if (!interfaceNode) { - // add interface it if it's missing + // add the interface if it's missing interfaceNode = common.statementFromString( `interface ${name} {}` ) as AstTypes.TSInterfaceDeclaration; diff --git a/packages/adders/lucia/config/adder.ts b/packages/adders/lucia/config/adder.ts index 3f2ef83b..f0f758dc 100644 --- a/packages/adders/lucia/config/adder.ts +++ b/packages/adders/lucia/config/adder.ts @@ -3,7 +3,7 @@ import { colors, dedent, defineAdderConfig, log, Walker } from '@svelte-cli/core import { common, exports, imports, variables, object, functions } from '@svelte-cli/core/js'; // eslint-disable-next-line no-duplicate-imports import type { AstTypes } from '@svelte-cli/core/js'; -import { addHooksHandle, getOrCreateAppInterface, hasTypeProp } from '../../common.ts'; +import { addHooksHandle, addGlobalAppInterface, hasTypeProp } from '../../common.ts'; const LUCIA_ADAPTER = { mysql: 'DrizzleMySQLAdapter', @@ -239,7 +239,7 @@ export const adder = defineAdderConfig({ condition: ({ typescript }) => typescript, contentType: 'script', content: ({ ast }) => { - const locals = getOrCreateAppInterface(ast, 'Locals'); + const locals = addGlobalAppInterface(ast, 'Locals'); if (!locals) { throw new Error('Failed detecting `locals` interface in `src/app.d.ts`'); diff --git a/packages/adders/supabase/config/adder.ts b/packages/adders/supabase/config/adder.ts index a52c484b..2665413d 100644 --- a/packages/adders/supabase/config/adder.ts +++ b/packages/adders/supabase/config/adder.ts @@ -6,7 +6,7 @@ import { type AstTypes } from '@svelte-cli/core'; import { options as availableOptions } from './options.ts'; -import { addHooksHandle, getOrCreateAppInterface, hasTypeProp } from '../../common.ts'; +import { addHooksHandle, addGlobalAppInterface, hasTypeProp } from '../../common.ts'; import { common, imports } from '@svelte-cli/core/js'; import { addFromRawHtml } from '@svelte-cli/core/html'; @@ -110,7 +110,7 @@ export const adder = defineAdderConfig({ if (isCli && isHelpers) imports.addNamed(ast, '$lib/supabase-types', { Database: 'Database' }, true); - const locals = getOrCreateAppInterface(ast, 'Locals'); + const locals = addGlobalAppInterface(ast, 'Locals'); if (!locals) { throw new Error('Failed detecting `locals` interface in `src/app.d.ts`'); } @@ -125,7 +125,7 @@ export const adder = defineAdderConfig({ if (!session) locals.body.body.push(createSessionType('session')); if (!user) locals.body.body.push(createUserType('user')); - const pageData = getOrCreateAppInterface(ast, 'PageData'); + const pageData = addGlobalAppInterface(ast, 'PageData'); if (!pageData) { throw new Error('Failed detecting `pageData` interface in `src/app.d.ts`'); } From d8bf2a0aeb56af840cf64e47138b2b1edeee0f68 Mon Sep 17 00:00:00 2001 From: AdrianGonz97 <31664583+AdrianGonz97@users.noreply.github.com> Date: Sun, 6 Oct 2024 19:23:23 -0400 Subject: [PATCH 62/76] provide highlighter for consistent `nextSteps` --- packages/adders/drizzle/config/adder.ts | 9 +++-- packages/adders/lucia/config/adder.ts | 6 ++-- packages/adders/supabase/config/adder.ts | 42 +++++++++++----------- packages/adders/supabase/config/options.ts | 6 ++-- packages/cli/commands/add.ts | 16 +++++---- packages/core/adder/config.ts | 21 ++++++----- packages/core/files/utils.ts | 13 ++++++- packages/core/internal.ts | 2 +- 8 files changed, 66 insertions(+), 49 deletions(-) diff --git a/packages/adders/drizzle/config/adder.ts b/packages/adders/drizzle/config/adder.ts index f9472611..ae4768f7 100644 --- a/packages/adders/drizzle/config/adder.ts +++ b/packages/adders/drizzle/config/adder.ts @@ -327,15 +327,14 @@ export const adder = defineAdderConfig({ } } ], - nextSteps: ({ options, colors }) => { - const highlight = (str: string) => colors.bold(colors.cyan(str)); + nextSteps: ({ options, highlighter }) => { const steps = [ - `You will need to set ${colors.yellow('DATABASE_URL')} in your production environment` + `You will need to set ${highlighter.env('DATABASE_URL')} in your production environment` ]; if (options.docker) { - steps.push(`Run ${highlight('npm run db:start')} to start the docker container`); + steps.push(`Run ${highlighter.command('npm run db:start')} to start the docker container`); } - steps.push(`To update your DB schema, run ${highlight('npm run db:push')}`); + steps.push(`To update your DB schema, run ${highlighter.command('npm run db:push')}`); return steps; } diff --git a/packages/adders/lucia/config/adder.ts b/packages/adders/lucia/config/adder.ts index f0f758dc..a24cc8ae 100644 --- a/packages/adders/lucia/config/adder.ts +++ b/packages/adders/lucia/config/adder.ts @@ -524,10 +524,10 @@ export const adder = defineAdderConfig({ } } ], - nextSteps: ({ colors, options }) => { - const steps = [`Run ${colors.bold(colors.cyan('npm run db:push'))} to update your database`]; + nextSteps: ({ highlighter, options }) => { + const steps = [`Run ${highlighter.command('npm run db:push')} to update your database`]; if (options.demo) { - steps.push(`Visit ${colors.bold('/demo')} route to view the demo`); + steps.push(`Visit ${highlighter.route('/demo')} route to view the demo`); } return steps; diff --git a/packages/adders/supabase/config/adder.ts b/packages/adders/supabase/config/adder.ts index 2665413d..3bc0af52 100644 --- a/packages/adders/supabase/config/adder.ts +++ b/packages/adders/supabase/config/adder.ts @@ -1,10 +1,4 @@ -import { - defineAdderConfig, - dedent, - type TextFileEditor, - colors, - type AstTypes -} from '@svelte-cli/core'; +import { defineAdderConfig, dedent, type TextFileEditor, type AstTypes } from '@svelte-cli/core'; import { options as availableOptions } from './options.ts'; import { addHooksHandle, addGlobalAppInterface, hasTypeProp } from '../../common.ts'; import { common, imports } from '@svelte-cli/core/js'; @@ -244,13 +238,13 @@ export const adder = defineAdderConfig({ `${kit?.routesDirectory}/auth/+page.server.${typescript ? 'ts' : 'js'}`, contentType: 'text', condition: ({ options }) => - options.auth.includes('basic') || options.auth.includes('magicLink'), + options.auth.includes('basic') || options.auth.includes('magic-link'), content: ({ options, typescript }) => { const isTs = typescript; const { auth, demo: isDemo } = options; const isBasic = auth.includes('basic'); - const isMagicLink = auth.includes('magicLink'); + const isMagicLink = auth.includes('magic-link'); return dedent` ${isBasic ? `import { redirect } from '@sveltejs/kit'` : ''} @@ -333,7 +327,7 @@ export const adder = defineAdderConfig({ const { auth } = options; const isBasic = auth.includes('basic'); - const isMagicLink = auth.includes('magicLink'); + const isMagicLink = auth.includes('magic-link'); return dedent` @@ -503,7 +497,7 @@ export const adder = defineAdderConfig({ `${kit?.routesDirectory}/auth/confirm/+server.${typescript ? 'ts' : 'js'}`, contentType: 'text', condition: ({ options }) => - options.auth.includes('basic') || options.auth.includes('magicLink'), + options.auth.includes('basic') || options.auth.includes('magic-link'), content: ({ typescript }) => { const isTs = typescript; @@ -590,7 +584,7 @@ export const adder = defineAdderConfig({ condition: ({ options }) => options.cli, content: ({ content, options }) => { const isBasic = options.auth.includes('basic'); - const isMagicLink = options.auth.includes('magicLink'); + const isMagicLink = options.auth.includes('magic-link'); content = content.replace('"http://127.0.0.1:3000"', '"http://localhost:5173"'); content = content.replace('"https://127.0.0.1:3000"', '"https://localhost:5173/*"'); @@ -649,7 +643,7 @@ export const adder = defineAdderConfig({ { name: () => './supabase/templates/magic_link.html', contentType: 'text', - condition: ({ options }) => options.cli && options.auth.includes('magicLink'), + condition: ({ options }) => options.cli && options.auth.includes('magic-link'), content: () => { return dedent` @@ -875,35 +869,39 @@ export const adder = defineAdderConfig({ } } ], - nextSteps: ({ options, workspace }) => { - const command = workspace.packageManager === 'npm' ? 'npx' : workspace.packageManager; + nextSteps: ({ options, packageManager, typescript, highlighter }) => { + const command = packageManager === 'npm' ? 'npx' : packageManager; const { auth, cli: isCli, helpers: isHelpers } = options; const isBasic = auth.includes('basic'); - const isMagicLink = auth.includes('magicLink'); + const isMagicLink = auth.includes('magic-link'); - const steps = ['Visit the Supabase docs: https://supabase.com/docs']; + const steps = [`Visit the Supabase docs: ${highlighter.website('https://supabase.com/docs')}`]; if (isCli) { - steps.push(`Start local Supabase services: ${colors.yellow(`${command} supabase start`)}`); + steps.push( + `Start local Supabase services: ${highlighter.command(`${command} supabase start`)}` + ); steps.push(dedent` - Changes to local Supabase config require a restart of the local services: ${colors.yellow(`${command} supabase stop`)} and ${colors.yellow(`${command} supabase start`)}`); + Changes to local Supabase config require a restart of the local services: ${highlighter.command(`${command} supabase stop`)} and ${highlighter.command(`${command} supabase start`)}`); } if (isHelpers) { steps.push(dedent` - Check out ${colors.green('package.json')} for the helper scripts. Remember to generate your database types`); + Check out ${highlighter.path('package.json')} for the helper scripts. Remember to generate your database types`); } if (isBasic || isMagicLink) { steps.push(dedent` - Update authGuard in ${colors.green(`./src/hooks.server.${workspace.typescript ? 'ts' : 'js'}`)} with your protected routes`); + Update authGuard in ${highlighter.path(`./src/hooks.server.${typescript ? 'ts' : 'js'}`)} with your protected routes`); } if (isBasic || isMagicLink) { steps.push(`Update your hosted project's email templates`); if (isCli) { - steps.push(`Local email templates are located in ${colors.green('./supabase/templates')}`); + steps.push( + `Local email templates are located in ${highlighter.path('./supabase/templates')}` + ); } } diff --git a/packages/adders/supabase/config/options.ts b/packages/adders/supabase/config/options.ts index 6a955f4b..48686511 100644 --- a/packages/adders/supabase/config/options.ts +++ b/packages/adders/supabase/config/options.ts @@ -4,7 +4,7 @@ export const options = defineAdderOptions({ auth: { question: 'What authentication methods would you like?', type: 'multiselect', - default: [] as unknown[], + default: [], options: [ { hint: 'Email and password', @@ -14,7 +14,7 @@ export const options = defineAdderOptions({ { hint: 'Magic link', label: 'Magic link', - value: 'magicLink' + value: 'magic-link' } ] }, @@ -39,6 +39,6 @@ export const options = defineAdderOptions({ type: 'boolean', default: false, condition: ({ auth }) => - (auth as unknown[]).includes('basic') || (auth as unknown[]).includes('magicLink') + (auth as unknown[]).includes('basic') || (auth as unknown[]).includes('magic-link') } }); diff --git a/packages/cli/commands/add.ts b/packages/cli/commands/add.ts index c09af09a..82997863 100644 --- a/packages/cli/commands/add.ts +++ b/packages/cli/commands/add.ts @@ -15,7 +15,12 @@ import { communityAdderIds, getCommunityAdder } from '@svelte-cli/adders'; -import { createOrUpdateFiles, createWorkspace, installPackages } from '@svelte-cli/core/internal'; +import { + createOrUpdateFiles, + createWorkspace, + installPackages, + getHighlighter +} from '@svelte-cli/core/internal'; import type { AdderWithoutExplicitArgs, OptionValues } from '@svelte-cli/core'; import * as common from '../common.js'; import { Directive, downloadPackage, getPackageJSON } from '../utils/fetch-packages.js'; @@ -469,6 +474,8 @@ export async function runAddCommand(options: Options, adders: string[]): Promise } } + const highlighter = getHighlighter(); + // print next steps const nextStepsMsg = selectedAdders .filter(({ adder }) => adder.config.nextSteps) @@ -481,12 +488,9 @@ export async function runAddCommand(options: Options, adders: string[]): Promise } const adderNextSteps = config.nextSteps!({ + ...workspace, options: official[metadata.id], - cwd: options.cwd, - colors: pc, - docs: metadata.website?.documentation, - packageManager: workspace.packageManager, - workspace + highlighter }); adderMessage += `- ${adderNextSteps.join('\n- ')}`; return adderMessage; diff --git a/packages/core/adder/config.ts b/packages/core/adder/config.ts index 38ca353c..9c5d3eaa 100644 --- a/packages/core/adder/config.ts +++ b/packages/core/adder/config.ts @@ -1,7 +1,6 @@ import type { OptionDefinition, OptionValues, Question } from './options.ts'; import type { FileType } from '../files/processors.ts'; import type { Workspace } from '../files/workspace.ts'; -import type { Colors } from 'picocolors/types.ts'; export type ConditionDefinition = ( Workspace: Workspace @@ -49,13 +48,19 @@ export type AdderConfig = { packages: Array>; scripts?: Array>; files: Array>; - nextSteps?: (data: { - options: OptionValues; - cwd: string; - colors: Colors; - docs: string | undefined; - workspace: Workspace; - }) => string[]; + nextSteps?: ( + data: { + highlighter: Highlighter; + } & Workspace + ) => string[]; +}; + +export type Highlighter = { + path: (str: string) => string; + command: (str: string) => string; + website: (str: string) => string; + route: (str: string) => string; + env: (str: string) => string; }; export function defineAdderConfig( diff --git a/packages/core/files/utils.ts b/packages/core/files/utils.ts index 96bc49ac..fc04bd0e 100644 --- a/packages/core/files/utils.ts +++ b/packages/core/files/utils.ts @@ -1,7 +1,8 @@ import fs from 'node:fs'; import path from 'node:path'; +import pc from 'picocolors'; import { parseJson, serializeJson } from '@svelte-cli/ast-tooling'; -import type { AdderConfig } from '../adder/config.ts'; +import type { AdderConfig, Highlighter } from '../adder/config.ts'; import type { OptionDefinition } from '../adder/options.ts'; import type { Workspace, WorkspaceWithoutExplicitArgs } from './workspace.ts'; @@ -128,3 +129,13 @@ export const commonFilePaths = { tsconfig: 'tsconfig.json', viteConfigTS: 'vite.config.ts' } as const; + +export function getHighlighter(): Highlighter { + return { + command: (str) => pc.bold(pc.cyanBright(str)), + env: (str) => pc.yellow(str), + path: (str) => pc.green(str), + route: (str) => pc.bold(pc.cyanBright(str)), + website: (str) => pc.whiteBright(str) + }; +} diff --git a/packages/core/internal.ts b/packages/core/internal.ts index 48d086d5..079554b5 100644 --- a/packages/core/internal.ts +++ b/packages/core/internal.ts @@ -1,4 +1,4 @@ -export { installPackages } from './files/utils.ts'; +export { installPackages, getHighlighter } from './files/utils.ts'; export { createOrUpdateFiles } from './files/processors.ts'; export { createWorkspace, From f10f94ef05509395300771ef1a08e101fe2c0989 Mon Sep 17 00:00:00 2001 From: AdrianGonz97 <31664583+AdrianGonz97@users.noreply.github.com> Date: Sun, 6 Oct 2024 19:23:41 -0400 Subject: [PATCH 63/76] cache packageManager --- packages/core/files/workspace.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/core/files/workspace.ts b/packages/core/files/workspace.ts index 8f9f82a2..99702022 100644 --- a/packages/core/files/workspace.ts +++ b/packages/core/files/workspace.ts @@ -108,12 +108,18 @@ function parseKitOptions(workspace: WorkspaceWithoutExplicitArgs) { return { routesDirectory, libDirectory }; } +let packageManager: AgentName | undefined; + /** * Guesses the package manager based on the detected lockfile or user-agent. * If neither of those return valid package managers, it falls back to `npm`. */ export async function detectPackageManager(cwd: string): Promise { + if (packageManager) return packageManager; + const pm = await detect({ cwd }); + if (pm?.name) packageManager = pm.name; + return pm?.name ?? getUserAgent() ?? 'npm'; } From 70e03ca6b3dcca141ed7006bdd1af016557963c1 Mon Sep 17 00:00:00 2001 From: AdrianGonz97 <31664583+AdrianGonz97@users.noreply.github.com> Date: Sun, 6 Oct 2024 19:34:53 -0400 Subject: [PATCH 64/76] tweaks --- packages/adders/supabase/config/options.ts | 3 +-- packages/core/adder/options.ts | 2 +- packages/core/files/utils.ts | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/adders/supabase/config/options.ts b/packages/adders/supabase/config/options.ts index 48686511..dc291ab4 100644 --- a/packages/adders/supabase/config/options.ts +++ b/packages/adders/supabase/config/options.ts @@ -38,7 +38,6 @@ export const options = defineAdderOptions({ question: 'Do you want to include demo routes to show protected routes?', type: 'boolean', default: false, - condition: ({ auth }) => - (auth as unknown[]).includes('basic') || (auth as unknown[]).includes('magic-link') + condition: ({ auth }) => auth.includes('basic') || auth.includes('magic-link') } }); diff --git a/packages/core/adder/options.ts b/packages/core/adder/options.ts index 73a469c7..c5805ae3 100644 --- a/packages/core/adder/options.ts +++ b/packages/core/adder/options.ts @@ -32,7 +32,7 @@ export type BaseQuestion = { * When this condition explicitly returns `false`, the question's value will * always be `undefined` and will not fallback to the specified `default` value. */ - condition?: (options: OptionValues) => boolean; + condition?: (options: any) => boolean; // TODO: we want to type `options` similar to OptionValues so that its option values can be inferred }; diff --git a/packages/core/files/utils.ts b/packages/core/files/utils.ts index fc04bd0e..550418b0 100644 --- a/packages/core/files/utils.ts +++ b/packages/core/files/utils.ts @@ -135,7 +135,7 @@ export function getHighlighter(): Highlighter { command: (str) => pc.bold(pc.cyanBright(str)), env: (str) => pc.yellow(str), path: (str) => pc.green(str), - route: (str) => pc.bold(pc.cyanBright(str)), + route: (str) => pc.bold(str), website: (str) => pc.whiteBright(str) }; } From d01c77f07c30e10e40305e2ccd9f00dc9c183af6 Mon Sep 17 00:00:00 2001 From: AdrianGonz97 <31664583+AdrianGonz97@users.noreply.github.com> Date: Sun, 6 Oct 2024 19:39:35 -0400 Subject: [PATCH 65/76] simplify further --- packages/core/files/utils.ts | 23 ++++++----------------- packages/core/files/workspace.ts | 6 ++---- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/packages/core/files/utils.ts b/packages/core/files/utils.ts index 550418b0..b207db40 100644 --- a/packages/core/files/utils.ts +++ b/packages/core/files/utils.ts @@ -3,8 +3,7 @@ import path from 'node:path'; import pc from 'picocolors'; import { parseJson, serializeJson } from '@svelte-cli/ast-tooling'; import type { AdderConfig, Highlighter } from '../adder/config.ts'; -import type { OptionDefinition } from '../adder/options.ts'; -import type { Workspace, WorkspaceWithoutExplicitArgs } from './workspace.ts'; +import type { Workspace } from './workspace.ts'; export type Package = { name: string; @@ -16,7 +15,7 @@ export type Package = { keywords?: string[]; }; -export function getPackageJson(workspace: WorkspaceWithoutExplicitArgs): { +export function getPackageJson(workspace: Workspace): { text: string; data: Package; } { @@ -40,7 +39,7 @@ export function getPackageJson(workspace: WorkspaceWithoutExplicitArgs): { }; } -export function readFile(workspace: WorkspaceWithoutExplicitArgs, filePath: string): string { +export function readFile(workspace: Workspace, filePath: string): string { const fullFilePath = getFilePath(workspace.cwd, filePath); if (!fileExistsWorkspace(workspace, filePath)) { @@ -52,10 +51,7 @@ export function readFile(workspace: WorkspaceWithoutExplicitArgs, filePath: stri return text; } -export function installPackages( - config: AdderConfig, - workspace: Workspace -): string { +export function installPackages(config: AdderConfig, workspace: Workspace): string { const { text: originalText, data } = getPackageJson(workspace); for (const dependency of config.packages) { @@ -94,11 +90,7 @@ function alphabetizeProperties(obj: Record) { return orderedObj; } -export function writeFile( - workspace: WorkspaceWithoutExplicitArgs, - filePath: string, - content: string -): void { +export function writeFile(workspace: Workspace, filePath: string, content: string): void { const fullFilePath = getFilePath(workspace.cwd, filePath); const fullDirectoryPath = path.dirname(fullFilePath); @@ -111,10 +103,7 @@ export function writeFile( fs.writeFileSync(fullFilePath, content, 'utf8'); } -export function fileExistsWorkspace( - workspace: WorkspaceWithoutExplicitArgs, - filePath: string -): boolean { +export function fileExistsWorkspace(workspace: Workspace, filePath: string): boolean { const fullFilePath = getFilePath(workspace.cwd, filePath); return fs.existsSync(fullFilePath); } diff --git a/packages/core/files/workspace.ts b/packages/core/files/workspace.ts index 99702022..f833eb3b 100644 --- a/packages/core/files/workspace.ts +++ b/packages/core/files/workspace.ts @@ -6,7 +6,7 @@ import { type AstTypes, parseScript } from '@svelte-cli/ast-tooling'; import { TESTING } from '../env.ts'; import { common, object } from '../tooling/js/index.ts'; import { commonFilePaths, getPackageJson, readFile } from './utils.ts'; -import type { OptionDefinition, OptionValues, Question } from '../adder/options.ts'; +import type { OptionDefinition, OptionValues } from '../adder/options.ts'; import { AGENTS, detect, type AgentName } from 'package-manager-detector'; export type Workspace = { @@ -19,8 +19,6 @@ export type Workspace = { packageManager: AgentName; }; -export type WorkspaceWithoutExplicitArgs = Workspace>; - export function createEmptyWorkspace() { return { options: {}, @@ -62,7 +60,7 @@ export async function createWorkspace( return workspace; } -function parseKitOptions(workspace: WorkspaceWithoutExplicitArgs) { +function parseKitOptions(workspace: Workspace) { const configSource = readFile(workspace, commonFilePaths.svelteConfig); const ast = parseScript(configSource); From c4f2fa1bdcbdef59522a54d143f7c4d3f850b3ef Mon Sep 17 00:00:00 2001 From: AdrianGonz97 <31664583+AdrianGonz97@users.noreply.github.com> Date: Sun, 6 Oct 2024 22:23:14 -0400 Subject: [PATCH 66/76] a few nits --- packages/cli/common.ts | 2 +- packages/core/files/workspace.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/common.ts b/packages/cli/common.ts index 48641eb6..e9ec7c65 100644 --- a/packages/cli/common.ts +++ b/packages/cli/common.ts @@ -4,8 +4,8 @@ import { exec } from 'tinyexec'; import * as p from '@svelte-cli/clack-prompts'; import { detect, AGENTS, type AgentName } from 'package-manager-detector'; import { COMMANDS, constructCommand, resolveCommand } from 'package-manager-detector/commands'; -import type { AdderWithoutExplicitArgs, Precondition } from '@svelte-cli/core'; import type { Argument, HelpConfiguration, Option } from 'commander'; +import type { AdderWithoutExplicitArgs, Precondition } from '@svelte-cli/core'; import { detectPackageManager, getUserAgent } from '@svelte-cli/core/internal'; export const helpConfig: HelpConfiguration = { diff --git a/packages/core/files/workspace.ts b/packages/core/files/workspace.ts index f833eb3b..514b219f 100644 --- a/packages/core/files/workspace.ts +++ b/packages/core/files/workspace.ts @@ -2,12 +2,12 @@ import fs from 'node:fs'; import path from 'node:path'; import * as find from 'empathic/find'; import * as resolve from 'empathic/resolve'; +import { AGENTS, detect, type AgentName } from 'package-manager-detector'; import { type AstTypes, parseScript } from '@svelte-cli/ast-tooling'; import { TESTING } from '../env.ts'; import { common, object } from '../tooling/js/index.ts'; import { commonFilePaths, getPackageJson, readFile } from './utils.ts'; import type { OptionDefinition, OptionValues } from '../adder/options.ts'; -import { AGENTS, detect, type AgentName } from 'package-manager-detector'; export type Workspace = { options: OptionValues; From b857da98ea1b3fda23ee122c556232efe8bc16a4 Mon Sep 17 00:00:00 2001 From: AdrianGonz97 <31664583+AdrianGonz97@users.noreply.github.com> Date: Sun, 6 Oct 2024 22:30:00 -0400 Subject: [PATCH 67/76] tab tweaks --- packages/adders/supabase/config/adder.ts | 46 ++++++++++++------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/adders/supabase/config/adder.ts b/packages/adders/supabase/config/adder.ts index 3bc0af52..1086385f 100644 --- a/packages/adders/supabase/config/adder.ts +++ b/packages/adders/supabase/config/adder.ts @@ -410,26 +410,26 @@ export const adder = defineAdderConfig({ const isTs = typescript; return dedent` - import { PUBLIC_BASE_URL } from '$env/static/public' - ${isTs ? `import type { Actions } from './$types'` : ''} - - export const actions${isTs ? ': Actions' : ''} = { - default: async ({ request, locals: { supabase } }) => { - const formData = await request.formData() - const email = formData.get('email')${isTs ? ' as string' : ''} - - const { error } = await supabase.auth.resetPasswordForEmail( - email, - { redirectTo: \`\${PUBLIC_BASE_URL}/auth/reset-password\` } - ) - if (error) { - console.error(error) - return { message: 'Something went wrong, please try again.' } - } else { - return { message: 'Please check your email inbox.' } - } - }, - } + import { PUBLIC_BASE_URL } from '$env/static/public' + ${isTs ? `import type { Actions } from './$types'` : ''} + + export const actions${isTs ? ': Actions' : ''} = { + default: async ({ request, locals: { supabase } }) => { + const formData = await request.formData() + const email = formData.get('email')${isTs ? ' as string' : ''} + + const { error } = await supabase.auth.resetPasswordForEmail( + email, + { redirectTo: \`\${PUBLIC_BASE_URL}/auth/reset-password\` } + ) + if (error) { + console.error(error) + return { message: 'Something went wrong, please try again.' } + } else { + return { message: 'Please check your email inbox.' } + } + }, + } `; } }, @@ -1084,9 +1084,9 @@ function createUserType(name: string): AstTypes.TSPropertySignature { } function getSupabaseHandleContent() { - return dedent` + return ` async ({ event, resolve }) => { - event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { + event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { cookies: { getAll: () => event.cookies.getAll(), setAll: (cookiesToSet) => { @@ -1125,7 +1125,7 @@ function getSupabaseHandleContent() { } function getAuthGuardHandleContent(isDemo: boolean) { - return dedent` + return ` async ({ event, resolve }) => { const { session, user } = await event.locals.safeGetSession() event.locals.session = session From d490afdd493418c51ba34e5a51ce26e488a16c34 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Mon, 7 Oct 2024 18:49:40 +0200 Subject: [PATCH 68/76] extract demos --- packages/adders/supabase/config/adder.ts | 193 +--------------------- packages/adders/supabase/config/demos.ts | 198 +++++++++++++++++++++++ 2 files changed, 200 insertions(+), 191 deletions(-) create mode 100644 packages/adders/supabase/config/demos.ts diff --git a/packages/adders/supabase/config/adder.ts b/packages/adders/supabase/config/adder.ts index 1086385f..b37b65fa 100644 --- a/packages/adders/supabase/config/adder.ts +++ b/packages/adders/supabase/config/adder.ts @@ -3,6 +3,7 @@ import { options as availableOptions } from './options.ts'; import { addHooksHandle, addGlobalAppInterface, hasTypeProp } from '../../common.ts'; import { common, imports } from '@svelte-cli/core/js'; import { addFromRawHtml } from '@svelte-cli/core/html'; +import { demos } from './demos.ts'; export const adder = defineAdderConfig({ metadata: { @@ -677,197 +678,7 @@ export const adder = defineAdderConfig({ `; } }, - // Demo routes when user has selected Basic Auth and/or Magic Link - { - name: ({ kit }) => `${kit?.routesDirectory}/+page.svelte`, - contentType: 'svelte', - condition: ({ options }) => options.demo, - content: ({ jsAst, htmlAst }) => { - imports.addNamed(jsAst, '$app/stores', { page: 'page' }); - - common.addFromString( - jsAst, - ` - async function logout() { - const { error } = await $page.data.supabase.auth.signOut(); - if (error) { - console.error(error); - } - }; - ` - ); - - addFromRawHtml( - htmlAst.childNodes, - dedent` -

      Welcome to SvelteKit with Supabase

      - - {#if $page.data.user} - Logout - {/if} -
      -							User: {JSON.stringify($page.data.user, null, 2)}
      -						
      - ` - ); - } - }, - { - name: ({ kit, typescript }) => - `${kit?.routesDirectory}/private/+layout.server.${typescript ? 'ts' : 'js'}`, - contentType: 'text', - condition: ({ options }) => options.demo, - content: () => { - return dedent` - /** - * This file is necessary to ensure protection of all routes in the \`private\` - * directory. It makes the routes in this directory _dynamic_ routes, which - * send a server request, and thus trigger \`hooks.server.ts\`. - **/ - `; - } - }, - { - name: ({ kit }) => `${kit?.routesDirectory}/private/+layout.svelte`, - contentType: 'text', - condition: ({ options }) => options.demo, - content: () => { - return dedent` - - -
      - - Logout -
      -
      - {@render children?.()} -
      - `; - } - }, - { - name: ({ kit }) => `${kit?.routesDirectory}/private/+page.svelte`, - contentType: 'text', - condition: ({ options }) => options.demo, - content: ({ options, typescript }) => { - const isTs = typescript; - const { cli: isCli } = options; - - return dedent` - - import { invalidate } from '$app/navigation' - - let { data } = $props(); - let { ${isCli ? 'notes, supabase, user' : 'user'} } = $derived(data); - - ${ - isCli - ? ` - async function handleSubmit(evt${isTs ? ': SubmitEvent' : ''}) { - evt.preventDefault(); - if (!evt.target) return; - - const form = evt.target${isTs ? ' as HTMLFormElement' : ''} - - const note = (new FormData(form).get('note') ?? '')${isTs ? ' as string' : ''} - if (!note) return; - - const { error } = await supabase.from('notes').insert({ note }); - if (error) console.error(error); - - invalidate('supabase:db:notes'); - form.reset(); - } - ` - : '' - } - - -

      Private page for user: {user?.email}

      - ${ - isCli - ? ` -

      Notes

      -
        - {#each notes as note} -
      • {note.note}
      • - {/each} -
      -
      - -
      - ` - : '' - } - `; - } - }, - { - name: ({ kit, typescript }) => - `${kit?.routesDirectory}/private/+page.server.${typescript ? 'ts' : 'js'}`, - contentType: 'text', - condition: ({ options }) => options.demo && options.cli, - content: ({ typescript }) => { - const isTs = typescript; - - return dedent` - ${isTs ? `import type { PageServerLoad } from './$types'\n` : ''} - export const load${isTs ? ': PageServerLoad' : ''} = async ({ depends, locals: { supabase } }) => { - depends('supabase:db:notes') - const { data: notes } = await supabase.from('notes').select('id,note').order('id') - return { notes: notes ?? [] } - } - `; - } - }, - { - name: () => './supabase/migrations/00000000000000_demo.sql', - contentType: 'text', - condition: ({ options }) => options.demo && options.cli, - content: () => { - return dedent` - create table notes ( - id bigint primary key generated always as identity, - created_at timestamp with time zone not null default now(), - user_id uuid references auth.users on delete cascade not null default auth.uid(), - note text not null - ); - - alter table notes enable row level security; - - revoke all on table notes from authenticated; - revoke all on table notes from anon; - - grant all (note) on table notes to authenticated; - grant select (id) on table notes to authenticated; - grant delete on table notes to authenticated; - - create policy "Users can access and modify their own notes" - on notes - for all - to authenticated - using ((select auth.uid()) = user_id); - `; - } - } + ...demos ], nextSteps: ({ options, packageManager, typescript, highlighter }) => { const command = packageManager === 'npm' ? 'npx' : packageManager; diff --git a/packages/adders/supabase/config/demos.ts b/packages/adders/supabase/config/demos.ts new file mode 100644 index 00000000..69e4cede --- /dev/null +++ b/packages/adders/supabase/config/demos.ts @@ -0,0 +1,198 @@ +import { dedent, type FileType } from '@svelte-cli/core'; +import type { options } from './options.ts'; +import { common, imports } from '@svelte-cli/core/js'; +import { addFromRawHtml } from '@svelte-cli/core/html'; + +export const demos: Array> = [ + // Demo routes when user has selected Basic Auth and/or Magic Link + { + name: ({ kit }) => `${kit?.routesDirectory}/+page.svelte`, + contentType: 'svelte', + condition: ({ options }) => options.demo, + content: ({ jsAst, htmlAst }) => { + imports.addNamed(jsAst, '$app/stores', { page: 'page' }); + + common.addFromString( + jsAst, + ` + async function logout() { + const { error } = await $page.data.supabase.auth.signOut(); + if (error) { + console.error(error); + } + }; + ` + ); + + addFromRawHtml( + htmlAst.childNodes, + dedent` +

      Welcome to SvelteKit with Supabase

      + + {#if $page.data.user} + Logout + {/if} +
      +                        User: {JSON.stringify($page.data.user, null, 2)}
      +                    
      + ` + ); + } + }, + { + name: ({ kit, typescript }) => + `${kit?.routesDirectory}/private/+layout.server.${typescript ? 'ts' : 'js'}`, + contentType: 'text', + condition: ({ options }) => options.demo, + content: () => { + return dedent` + /** + * This file is necessary to ensure protection of all routes in the \`private\` + * directory. It makes the routes in this directory _dynamic_ routes, which + * send a server request, and thus trigger \`hooks.server.ts\`. + **/ + `; + } + }, + { + name: ({ kit }) => `${kit?.routesDirectory}/private/+layout.svelte`, + contentType: 'text', + condition: ({ options }) => options.demo, + content: () => { + return dedent` + + +
      + + Logout +
      +
      + {@render children?.()} +
      + `; + } + }, + { + name: ({ kit }) => `${kit?.routesDirectory}/private/+page.svelte`, + contentType: 'text', + condition: ({ options }) => options.demo, + content: ({ options, typescript }) => { + const isTs = typescript; + const { cli: isCli } = options; + + return dedent` + + import { invalidate } from '$app/navigation' + + let { data } = $props(); + let { ${isCli ? 'notes, supabase, user' : 'user'} } = $derived(data); + + ${ + isCli + ? ` + async function handleSubmit(evt${isTs ? ': SubmitEvent' : ''}) { + evt.preventDefault(); + if (!evt.target) return; + + const form = evt.target${isTs ? ' as HTMLFormElement' : ''} + + const note = (new FormData(form).get('note') ?? '')${isTs ? ' as string' : ''} + if (!note) return; + + const { error } = await supabase.from('notes').insert({ note }); + if (error) console.error(error); + + invalidate('supabase:db:notes'); + form.reset(); + } + ` + : '' + } + + +

      Private page for user: {user?.email}

      + ${ + isCli + ? ` +

      Notes

      +
        + {#each notes as note} +
      • {note.note}
      • + {/each} +
      +
      + +
      + ` + : '' + } + `; + } + }, + { + name: ({ kit, typescript }) => + `${kit?.routesDirectory}/private/+page.server.${typescript ? 'ts' : 'js'}`, + contentType: 'text', + condition: ({ options }) => options.demo && options.cli, + content: ({ typescript }) => { + const isTs = typescript; + + return dedent` + ${isTs ? `import type { PageServerLoad } from './$types'\n` : ''} + export const load${isTs ? ': PageServerLoad' : ''} = async ({ depends, locals: { supabase } }) => { + depends('supabase:db:notes') + const { data: notes } = await supabase.from('notes').select('id,note').order('id') + return { notes: notes ?? [] } + } + `; + } + }, + { + name: () => './supabase/migrations/00000000000000_demo.sql', + contentType: 'text', + condition: ({ options }) => options.demo && options.cli, + content: () => { + return dedent` + create table notes ( + id bigint primary key generated always as identity, + created_at timestamp with time zone not null default now(), + user_id uuid references auth.users on delete cascade not null default auth.uid(), + note text not null + ); + + alter table notes enable row level security; + + revoke all on table notes from authenticated; + revoke all on table notes from anon; + + grant all (note) on table notes to authenticated; + grant select (id) on table notes to authenticated; + grant delete on table notes to authenticated; + + create policy "Users can access and modify their own notes" + on notes + for all + to authenticated + using ((select auth.uid()) = user_id); + `; + } + } +]; From 0010510a73d66d06b82035ae5b3d020551f7b018 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Mon, 7 Oct 2024 18:53:50 +0200 Subject: [PATCH 69/76] extract helpers --- packages/adders/supabase/config/adder.ts | 252 +-------------------- packages/adders/supabase/config/helpers.ts | 245 ++++++++++++++++++++ 2 files changed, 256 insertions(+), 241 deletions(-) create mode 100644 packages/adders/supabase/config/helpers.ts diff --git a/packages/adders/supabase/config/adder.ts b/packages/adders/supabase/config/adder.ts index b37b65fa..dae80dcc 100644 --- a/packages/adders/supabase/config/adder.ts +++ b/packages/adders/supabase/config/adder.ts @@ -1,9 +1,19 @@ -import { defineAdderConfig, dedent, type TextFileEditor, type AstTypes } from '@svelte-cli/core'; +import { defineAdderConfig, dedent } from '@svelte-cli/core'; import { options as availableOptions } from './options.ts'; import { addHooksHandle, addGlobalAppInterface, hasTypeProp } from '../../common.ts'; import { common, imports } from '@svelte-cli/core/js'; import { addFromRawHtml } from '@svelte-cli/core/html'; import { demos } from './demos.ts'; +import { + appendContent, + createSafeGetSessionType, + createSessionType, + createSupabaseType, + createUserType, + generateEnvFileContent, + getAuthGuardHandleContent, + getSupabaseHandleContent +} from './helpers.ts'; export const adder = defineAdderConfig({ metadata: { @@ -719,243 +729,3 @@ export const adder = defineAdderConfig({ return steps; } }); - -function generateEnvFileContent({ content, options }: TextFileEditor) { - const isCli = options.cli; - - content = addEnvVar(content, 'PUBLIC_BASE_URL', '"http://localhost:5173"'); - content = addEnvVar( - content, - 'PUBLIC_SUPABASE_URL', - // Local development env always has the same credentials, prepopulate the local dev env file - isCli ? '"http://127.0.0.1:54321"' : '""' - ); - content = addEnvVar( - content, - 'PUBLIC_SUPABASE_ANON_KEY', - // Local development env always has the same credentials, prepopulate the local dev env file - isCli - ? '"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0"' - : '""' - ); - - content = options.admin - ? addEnvVar( - content, - 'SUPABASE_SERVICE_ROLE_KEY', - // Local development env always has the same credentials, prepopulate the local dev env file - isCli - ? '"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU"' - : '""' - ) - : content; - - return content; -} - -function addEnvVar(content: string, key: string, value: string) { - if (!content.includes(key + '=')) { - content = appendContent(content, `${key}=${value}`); - } - return content; -} - -function appendContent(existing: string, content: string) { - const withNewLine = !existing.length || existing.endsWith('\n') ? existing : existing + '\n'; - return withNewLine + content + '\n'; -} - -function createSupabaseType( - name: string, - typescript: boolean -): AstTypes.TSInterfaceBody['body'][number] { - return { - type: 'TSPropertySignature', - key: { - type: 'Identifier', - name - }, - typeAnnotation: { - type: 'TSTypeAnnotation', - typeAnnotation: { - type: 'TSTypeReference', - typeName: { - type: 'Identifier', - name: 'SupabaseClient' - }, - typeParameters: typescript - ? { - type: 'TSTypeParameterInstantiation', - params: [ - { - type: 'TSTypeReference', - typeName: { - type: 'Identifier', - name: 'Database' - } - } - ] - } - : undefined - } - } - }; -} - -function createSafeGetSessionType(name: string): AstTypes.TSInterfaceBody['body'][number] { - return { - type: 'TSPropertySignature', - key: { - type: 'Identifier', - name - }, - typeAnnotation: { - type: 'TSTypeAnnotation', - typeAnnotation: { - type: 'TSFunctionType', - typeAnnotation: { - type: 'TSTypeAnnotation', - typeAnnotation: { - type: 'TSTypeReference', - typeName: { - type: 'Identifier', - name: 'Promise' - }, - typeParameters: { - type: 'TSTypeParameterInstantiation', - params: [ - { - type: 'TSTypeLiteral', - members: [createSessionType('session'), createUserType('user')] - } - ] - } - } - }, - parameters: [] - } - } - }; -} - -function createSessionType(name: string): AstTypes.TSPropertySignature { - return { - type: 'TSPropertySignature', - key: { - type: 'Identifier', - name - }, - typeAnnotation: { - type: 'TSTypeAnnotation', - typeAnnotation: { - type: 'TSUnionType', - types: [ - { - type: 'TSTypeReference', - typeName: { - type: 'Identifier', - name: 'Session' - } - }, - { - type: 'TSNullKeyword' - } - ] - } - } - }; -} - -function createUserType(name: string): AstTypes.TSPropertySignature { - return { - type: 'TSPropertySignature', - key: { - type: 'Identifier', - name - }, - typeAnnotation: { - type: 'TSTypeAnnotation', - typeAnnotation: { - type: 'TSUnionType', - types: [ - { - type: 'TSTypeReference', - typeName: { - type: 'Identifier', - name: 'User' - } - }, - { - type: 'TSNullKeyword' - } - ] - } - } - }; -} - -function getSupabaseHandleContent() { - return ` - async ({ event, resolve }) => { - event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { - cookies: { - getAll: () => event.cookies.getAll(), - setAll: (cookiesToSet) => { - cookiesToSet.forEach(({ name, value, options }) => { - event.cookies.set(name, value, { ...options, path: '/' }) - }) - }, - }, - }) - - event.locals.safeGetSession = async () => { - const { - data: { session }, - } = await event.locals.supabase.auth.getSession() - if (!session) { - return { session: null, user: null } - } - - const { - data: { user }, - error, - } = await event.locals.supabase.auth.getUser() - if (error) { - return { session: null, user: null } - } - - return { session, user } - } - - return resolve(event, { - filterSerializedResponseHeaders(name) { - return name === 'content-range' || name === 'x-supabase-api-version' - }, - }); - };`; -} - -function getAuthGuardHandleContent(isDemo: boolean) { - return ` - async ({ event, resolve }) => { - const { session, user } = await event.locals.safeGetSession() - event.locals.session = session - event.locals.user = user - ${ - isDemo - ? ` - if (!event.locals.session && event.url.pathname.startsWith('/private')) { - redirect(303, '/auth') - } - - if (event.locals.session && event.url.pathname === '/auth') { - redirect(303, '/private') - } - ` - : ` - // Add authentication guards here - ` - } - return resolve(event); - }`; -} diff --git a/packages/adders/supabase/config/helpers.ts b/packages/adders/supabase/config/helpers.ts new file mode 100644 index 00000000..809408e5 --- /dev/null +++ b/packages/adders/supabase/config/helpers.ts @@ -0,0 +1,245 @@ +import type { AstTypes, TextFileEditor } from '@svelte-cli/core'; +import { options as availableOptions } from './options.ts'; + +export function generateEnvFileContent({ + content, + options +}: TextFileEditor) { + const isCli = options.cli; + + content = addEnvVar(content, 'PUBLIC_BASE_URL', '"http://localhost:5173"'); + content = addEnvVar( + content, + 'PUBLIC_SUPABASE_URL', + // Local development env always has the same credentials, prepopulate the local dev env file + isCli ? '"http://127.0.0.1:54321"' : '""' + ); + content = addEnvVar( + content, + 'PUBLIC_SUPABASE_ANON_KEY', + // Local development env always has the same credentials, prepopulate the local dev env file + isCli + ? '"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0"' + : '""' + ); + + content = options.admin + ? addEnvVar( + content, + 'SUPABASE_SERVICE_ROLE_KEY', + // Local development env always has the same credentials, prepopulate the local dev env file + isCli + ? '"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU"' + : '""' + ) + : content; + + return content; +} + +export function addEnvVar(content: string, key: string, value: string) { + if (!content.includes(key + '=')) { + content = appendContent(content, `${key}=${value}`); + } + return content; +} + +export function appendContent(existing: string, content: string) { + const withNewLine = !existing.length || existing.endsWith('\n') ? existing : existing + '\n'; + return withNewLine + content + '\n'; +} + +export function createSupabaseType( + name: string, + typescript: boolean +): AstTypes.TSInterfaceBody['body'][number] { + return { + type: 'TSPropertySignature', + key: { + type: 'Identifier', + name + }, + typeAnnotation: { + type: 'TSTypeAnnotation', + typeAnnotation: { + type: 'TSTypeReference', + typeName: { + type: 'Identifier', + name: 'SupabaseClient' + }, + typeParameters: typescript + ? { + type: 'TSTypeParameterInstantiation', + params: [ + { + type: 'TSTypeReference', + typeName: { + type: 'Identifier', + name: 'Database' + } + } + ] + } + : undefined + } + } + }; +} + +export function createSafeGetSessionType(name: string): AstTypes.TSInterfaceBody['body'][number] { + return { + type: 'TSPropertySignature', + key: { + type: 'Identifier', + name + }, + typeAnnotation: { + type: 'TSTypeAnnotation', + typeAnnotation: { + type: 'TSFunctionType', + typeAnnotation: { + type: 'TSTypeAnnotation', + typeAnnotation: { + type: 'TSTypeReference', + typeName: { + type: 'Identifier', + name: 'Promise' + }, + typeParameters: { + type: 'TSTypeParameterInstantiation', + params: [ + { + type: 'TSTypeLiteral', + members: [createSessionType('session'), createUserType('user')] + } + ] + } + } + }, + parameters: [] + } + } + }; +} + +export function createSessionType(name: string): AstTypes.TSPropertySignature { + return { + type: 'TSPropertySignature', + key: { + type: 'Identifier', + name + }, + typeAnnotation: { + type: 'TSTypeAnnotation', + typeAnnotation: { + type: 'TSUnionType', + types: [ + { + type: 'TSTypeReference', + typeName: { + type: 'Identifier', + name: 'Session' + } + }, + { + type: 'TSNullKeyword' + } + ] + } + } + }; +} + +export function createUserType(name: string): AstTypes.TSPropertySignature { + return { + type: 'TSPropertySignature', + key: { + type: 'Identifier', + name + }, + typeAnnotation: { + type: 'TSTypeAnnotation', + typeAnnotation: { + type: 'TSUnionType', + types: [ + { + type: 'TSTypeReference', + typeName: { + type: 'Identifier', + name: 'User' + } + }, + { + type: 'TSNullKeyword' + } + ] + } + } + }; +} + +export function getSupabaseHandleContent() { + return ` + async ({ event, resolve }) => { + event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { + cookies: { + getAll: () => event.cookies.getAll(), + setAll: (cookiesToSet) => { + cookiesToSet.forEach(({ name, value, options }) => { + event.cookies.set(name, value, { ...options, path: '/' }) + }) + }, + }, + }) + + event.locals.safeGetSession = async () => { + const { + data: { session }, + } = await event.locals.supabase.auth.getSession() + if (!session) { + return { session: null, user: null } + } + + const { + data: { user }, + error, + } = await event.locals.supabase.auth.getUser() + if (error) { + return { session: null, user: null } + } + + return { session, user } + } + + return resolve(event, { + filterSerializedResponseHeaders(name) { + return name === 'content-range' || name === 'x-supabase-api-version' + }, + }); + };`; +} + +export function getAuthGuardHandleContent(isDemo: boolean) { + return ` + async ({ event, resolve }) => { + const { session, user } = await event.locals.safeGetSession() + event.locals.session = session + event.locals.user = user + ${ + isDemo + ? ` + if (!event.locals.session && event.url.pathname.startsWith('/private')) { + redirect(303, '/auth') + } + + if (event.locals.session && event.url.pathname === '/auth') { + redirect(303, '/private') + } + ` + : ` + // Add authentication guards here + ` + } + return resolve(event); + }`; +} From 3d689cdb7ca0b93a0d4e5c2b834df9644c350ba3 Mon Sep 17 00:00:00 2001 From: AdrianGonz97 <31664583+AdrianGonz97@users.noreply.github.com> Date: Mon, 7 Oct 2024 18:47:07 -0400 Subject: [PATCH 70/76] simplify --- packages/adders/supabase/config/adder.ts | 121 ++++++++------------- packages/adders/supabase/config/demos.ts | 23 ++-- packages/adders/supabase/config/helpers.ts | 16 +-- 3 files changed, 61 insertions(+), 99 deletions(-) diff --git a/packages/adders/supabase/config/adder.ts b/packages/adders/supabase/config/adder.ts index dae80dcc..6cafc774 100644 --- a/packages/adders/supabase/config/adder.ts +++ b/packages/adders/supabase/config/adder.ts @@ -37,12 +37,7 @@ export const adder = defineAdderConfig({ condition: ({ options }) => options.auth.length > 0 }, // Local development CLI - { - name: 'supabase', - version: '^1.191.3', - dev: true, - condition: ({ options }) => options.cli - } + { name: 'supabase', version: '^1.191.3', dev: true, condition: ({ options }) => options.cli } ], scripts: [ { @@ -75,13 +70,9 @@ export const adder = defineAdderConfig({ contentType: 'script', condition: ({ options }) => options.auth.length > 0, content: ({ ast, options, typescript }) => { - const isTs = typescript; - const { demo: isDemo } = options; - imports.addNamed(ast, '@supabase/ssr', { createServerClient: 'createServerClient' }); - if (!isTs && isDemo) imports.addNamed(ast, '@sveltejs/kit', { redirect: 'redirect' }); - if (isTs && isDemo) { + if (options.demo) { imports.addNamed(ast, '@sveltejs/kit', { redirect: 'redirect' }); } @@ -91,7 +82,7 @@ export const adder = defineAdderConfig({ }); addHooksHandle(ast, typescript, 'supabase', getSupabaseHandleContent(), true); - addHooksHandle(ast, typescript, 'authGuard', getAuthGuardHandleContent(isDemo)); + addHooksHandle(ast, typescript, 'authGuard', getAuthGuardHandleContent(options.demo)); } }, { @@ -99,8 +90,6 @@ export const adder = defineAdderConfig({ contentType: 'script', condition: ({ options, typescript }) => typescript && options.auth.length > 0, content: ({ ast, options, typescript }) => { - const { cli: isCli, helpers: isHelpers } = options; - imports.addNamed( ast, '@supabase/supabase-js', @@ -112,8 +101,9 @@ export const adder = defineAdderConfig({ true ); - if (isCli && isHelpers) + if (options.cli && options.helpers) { imports.addNamed(ast, '$lib/supabase-types', { Database: 'Database' }, true); + } const locals = addGlobalAppInterface(ast, 'Locals'); if (!locals) { @@ -251,27 +241,24 @@ export const adder = defineAdderConfig({ condition: ({ options }) => options.auth.includes('basic') || options.auth.includes('magic-link'), content: ({ options, typescript }) => { - const isTs = typescript; - const { auth, demo: isDemo } = options; - - const isBasic = auth.includes('basic'); - const isMagicLink = auth.includes('magic-link'); + const isBasic = options.auth.includes('basic'); + const isMagicLink = options.auth.includes('magic-link'); return dedent` ${isBasic ? `import { redirect } from '@sveltejs/kit'` : ''} - ${isDemo ? `import { PUBLIC_BASE_URL } from '$env/static/public'` : ''} - ${isTs ? `import type { Actions } from './$types'` : ''} + ${options.demo ? `import { PUBLIC_BASE_URL } from '$env/static/public'` : ''} + ${typescript ? `import type { Actions } from './$types'` : ''} - export const actions${isTs ? ': Actions' : ''} = {${ + export const actions${typescript ? ': Actions' : ''} = {${ isBasic ? ` signup: async ({ request, locals: { supabase } }) => { const formData = await request.formData() - const email = formData.get('email')${isTs ? ' as string' : ''} - const password = formData.get('password')${isTs ? ' as string' : ''} + const email = formData.get('email')${typescript ? ' as string' : ''} + const password = formData.get('password')${typescript ? ' as string' : ''} const { error } = await supabase.auth.signUp({${ - isDemo + options.demo ? ` email, password, @@ -286,7 +273,7 @@ export const adder = defineAdderConfig({ return { message: 'Something went wrong, please try again.' } } else { ${ - isDemo + options.demo ? `// Redirect to local Inbucket for demo purposes redirect(303, \`http://localhost:54324/m/\${email}\`)` : `return { message: 'Sign up succeeded! Please check your email inbox.' }` @@ -295,8 +282,8 @@ export const adder = defineAdderConfig({ }, login: async ({ request, locals: { supabase } }) => { const formData = await request.formData() - const email = formData.get('email')${isTs ? ' as string' : ''} - const password = formData.get('password')${isTs ? ' as string' : ''} + const email = formData.get('email')${typescript ? ' as string' : ''} + const password = formData.get('password')${typescript ? ' as string' : ''} const { error } = await supabase.auth.signInWithPassword({ email, password }) if (error) { @@ -304,7 +291,7 @@ export const adder = defineAdderConfig({ return { message: 'Something went wrong, please try again.' } } - redirect(303, '${isDemo ? '/private' : '/'}') + redirect(303, '${options.demo ? '/private' : '/'}') },` : '' }${ @@ -312,7 +299,7 @@ export const adder = defineAdderConfig({ ? ` magic: async ({ request, locals: { supabase } }) => { const formData = await request.formData() - const email = formData.get('email')${isTs ? ' as string' : ''} + const email = formData.get('email')${typescript ? ' as string' : ''} const { error } = await supabase.auth.signInWithOtp({ email }) if (error) { @@ -334,18 +321,15 @@ export const adder = defineAdderConfig({ contentType: 'text', condition: ({ options }) => options.auth.length > 0, content: ({ options, typescript }) => { - const isTs = typescript; - const { auth } = options; - - const isBasic = auth.includes('basic'); - const isMagicLink = auth.includes('magic-link'); + const isBasic = options.auth.includes('basic'); + const isMagicLink = options.auth.includes('magic-link'); return dedent` - + ${isBasic || isMagicLink ? `import { enhance } from '$app/forms'` : ''} - ${(isBasic || isMagicLink) && isTs ? `import type { ActionData } from './$types'` : ''} + ${(isBasic || isMagicLink) && typescript ? `import type { ActionData } from './$types'` : ''} - ${isBasic || isMagicLink ? `export let form${isTs ? ': ActionData' : ''}` : ''} + ${isBasic || isMagicLink ? `export let form${typescript ? ': ActionData' : ''}` : ''}
      @@ -387,15 +371,13 @@ export const adder = defineAdderConfig({ contentType: 'text', condition: ({ options }) => options.auth.includes('basic'), content: ({ typescript }) => { - const isTs = typescript; - return dedent` - + import { enhance } from '$app/forms' - ${isTs ? `import type { ActionData } from './$types'` : ''} + ${typescript ? `import type { ActionData } from './$types'` : ''} - export let form${isTs ? ': ActionData' : ''} + export let form${typescript ? ': ActionData' : ''} @@ -418,16 +400,14 @@ export const adder = defineAdderConfig({ contentType: 'text', condition: ({ options }) => options.auth.includes('basic'), content: ({ typescript }) => { - const isTs = typescript; - return dedent` import { PUBLIC_BASE_URL } from '$env/static/public' - ${isTs ? `import type { Actions } from './$types'` : ''} + ${typescript ? `import type { Actions } from './$types'` : ''} - export const actions${isTs ? ': Actions' : ''} = { + export const actions${typescript ? ': Actions' : ''} = { default: async ({ request, locals: { supabase } }) => { const formData = await request.formData() - const email = formData.get('email')${isTs ? ' as string' : ''} + const email = formData.get('email')${typescript ? ' as string' : ''} const { error } = await supabase.auth.resetPasswordForEmail( email, @@ -450,15 +430,13 @@ export const adder = defineAdderConfig({ contentType: 'text', condition: ({ options }) => options.auth.includes('basic'), content: ({ typescript }) => { - const isTs = typescript; - return dedent` - ${isTs ? `import type { Actions } from './$types'` : ''} + ${typescript ? `import type { Actions } from './$types'` : ''} - export const actions${isTs ? ': Actions' : ''} = { + export const actions${typescript ? ': Actions' : ''} = { default: async ({ request, locals: { supabase } }) => { const formData = await request.formData() - const password = formData.get('password')${isTs ? ' as string' : ''} + const password = formData.get('password')${typescript ? ' as string' : ''} const { error } = await supabase.auth.updateUser({ password }) if (error) { @@ -477,15 +455,13 @@ export const adder = defineAdderConfig({ contentType: 'text', condition: ({ options }) => options.auth.includes('basic'), content: ({ typescript }) => { - const isTs = typescript; - return dedent` - + import { enhance } from '$app/forms' - ${isTs ? `import type { ActionData } from './$types'` : ''} + ${typescript ? `import type { ActionData } from './$types'` : ''} - export let form${isTs ? ': ActionData' : ''} + export let form${typescript ? ': ActionData' : ''} @@ -510,20 +486,18 @@ export const adder = defineAdderConfig({ condition: ({ options }) => options.auth.includes('basic') || options.auth.includes('magic-link'), content: ({ typescript }) => { - const isTs = typescript; - return dedent` import { error, redirect } from '@sveltejs/kit' import { PUBLIC_BASE_URL } from '$env/static/public' ${ - isTs + typescript ? dedent`import type { EmailOtpType } from '@supabase/supabase-js' import type { RequestHandler } from './$types'\n` : '' } - export const GET${isTs ? ': RequestHandler' : ''} = async ({ url, locals: { supabase } }) => { + export const GET${typescript ? ': RequestHandler' : ''} = async ({ url, locals: { supabase } }) => { const token_hash = url.searchParams.get('token_hash') - const type = url.searchParams.get('type')${isTs ? ' as EmailOtpType | null' : ''} + const type = url.searchParams.get('type')${typescript ? ' as EmailOtpType | null' : ''} const next = url.searchParams.get('next') ?? \`\${PUBLIC_BASE_URL}/\` const redirectTo = new URL(next) @@ -549,16 +523,14 @@ export const adder = defineAdderConfig({ contentType: 'text', condition: ({ options }) => options.admin, content: ({ options, typescript }) => { - const isTs = typescript; - const { cli: isCli, helpers: isHelpers } = options; - + const annotate = typescript && options.cli && options.helpers; return dedent` import { PUBLIC_SUPABASE_URL } from '$env/static/public' import { SUPABASE_SERVICE_ROLE_KEY } from '$env/static/private' - ${isTs && isCli && isHelpers ? `import type { Database } from '$lib/supabase-types'` : ''} + ${annotate ? `import type { Database } from '$lib/supabase-types'` : ''} import { createClient } from '@supabase/supabase-js' - export const supabaseAdmin = createClient${isTs && isCli && isHelpers ? '' : ''}( + export const supabaseAdmin = createClient${annotate ? '' : ''}( PUBLIC_SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, { @@ -692,13 +664,12 @@ export const adder = defineAdderConfig({ ], nextSteps: ({ options, packageManager, typescript, highlighter }) => { const command = packageManager === 'npm' ? 'npx' : packageManager; - const { auth, cli: isCli, helpers: isHelpers } = options; - const isBasic = auth.includes('basic'); - const isMagicLink = auth.includes('magic-link'); + const isBasic = options.auth.includes('basic'); + const isMagicLink = options.auth.includes('magic-link'); const steps = [`Visit the Supabase docs: ${highlighter.website('https://supabase.com/docs')}`]; - if (isCli) { + if (options.cli) { steps.push( `Start local Supabase services: ${highlighter.command(`${command} supabase start`)}` ); @@ -706,7 +677,7 @@ export const adder = defineAdderConfig({ Changes to local Supabase config require a restart of the local services: ${highlighter.command(`${command} supabase stop`)} and ${highlighter.command(`${command} supabase start`)}`); } - if (isHelpers) { + if (options.helpers) { steps.push(dedent` Check out ${highlighter.path('package.json')} for the helper scripts. Remember to generate your database types`); } @@ -719,7 +690,7 @@ export const adder = defineAdderConfig({ if (isBasic || isMagicLink) { steps.push(`Update your hosted project's email templates`); - if (isCli) { + if (options.cli) { steps.push( `Local email templates are located in ${highlighter.path('./supabase/templates')}` ); diff --git a/packages/adders/supabase/config/demos.ts b/packages/adders/supabase/config/demos.ts index 69e4cede..ba889afc 100644 --- a/packages/adders/supabase/config/demos.ts +++ b/packages/adders/supabase/config/demos.ts @@ -92,26 +92,23 @@ export const demos: Array> = [ contentType: 'text', condition: ({ options }) => options.demo, content: ({ options, typescript }) => { - const isTs = typescript; - const { cli: isCli } = options; - return dedent` - + import { invalidate } from '$app/navigation' let { data } = $props(); - let { ${isCli ? 'notes, supabase, user' : 'user'} } = $derived(data); + let { ${options.cli ? 'notes, supabase, user' : 'user'} } = $derived(data); ${ - isCli + options.cli ? ` - async function handleSubmit(evt${isTs ? ': SubmitEvent' : ''}) { + async function handleSubmit(evt${typescript ? ': SubmitEvent' : ''}) { evt.preventDefault(); if (!evt.target) return; - const form = evt.target${isTs ? ' as HTMLFormElement' : ''} + const form = evt.target${typescript ? ' as HTMLFormElement' : ''} - const note = (new FormData(form).get('note') ?? '')${isTs ? ' as string' : ''} + const note = (new FormData(form).get('note') ?? '')${typescript ? ' as string' : ''} if (!note) return; const { error } = await supabase.from('notes').insert({ note }); @@ -127,7 +124,7 @@ export const demos: Array> = [

      Private page for user: {user?.email}

      ${ - isCli + options.cli ? `

      Notes

        @@ -153,11 +150,9 @@ export const demos: Array> = [ contentType: 'text', condition: ({ options }) => options.demo && options.cli, content: ({ typescript }) => { - const isTs = typescript; - return dedent` - ${isTs ? `import type { PageServerLoad } from './$types'\n` : ''} - export const load${isTs ? ': PageServerLoad' : ''} = async ({ depends, locals: { supabase } }) => { + ${typescript ? `import type { PageServerLoad } from './$types'\n` : ''} + export const load${typescript ? ': PageServerLoad' : ''} = async ({ depends, locals: { supabase } }) => { depends('supabase:db:notes') const { data: notes } = await supabase.from('notes').select('id,note').order('id') return { notes: notes ?? [] } diff --git a/packages/adders/supabase/config/helpers.ts b/packages/adders/supabase/config/helpers.ts index 809408e5..3364223d 100644 --- a/packages/adders/supabase/config/helpers.ts +++ b/packages/adders/supabase/config/helpers.ts @@ -1,24 +1,20 @@ import type { AstTypes, TextFileEditor } from '@svelte-cli/core'; -import { options as availableOptions } from './options.ts'; - -export function generateEnvFileContent({ - content, - options -}: TextFileEditor) { - const isCli = options.cli; +import type { options } from './options.ts'; +export function generateEnvFileContent(editor: TextFileEditor) { + let { content, options } = editor; content = addEnvVar(content, 'PUBLIC_BASE_URL', '"http://localhost:5173"'); content = addEnvVar( content, 'PUBLIC_SUPABASE_URL', // Local development env always has the same credentials, prepopulate the local dev env file - isCli ? '"http://127.0.0.1:54321"' : '""' + options.cli ? '"http://127.0.0.1:54321"' : '""' ); content = addEnvVar( content, 'PUBLIC_SUPABASE_ANON_KEY', // Local development env always has the same credentials, prepopulate the local dev env file - isCli + options.cli ? '"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0"' : '""' ); @@ -28,7 +24,7 @@ export function generateEnvFileContent({ content, 'SUPABASE_SERVICE_ROLE_KEY', // Local development env always has the same credentials, prepopulate the local dev env file - isCli + options.cli ? '"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU"' : '""' ) From ce115f493dbf342a6452c09c78f8bf2d4e6bf64a Mon Sep 17 00:00:00 2001 From: Manuel <30698007+manuel3108@users.noreply.github.com> Date: Tue, 8 Oct 2024 18:13:48 +0200 Subject: [PATCH 71/76] Make children in layout non optional --- packages/adders/supabase/config/adder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/adders/supabase/config/adder.ts b/packages/adders/supabase/config/adder.ts index 6cafc774..1126506a 100644 --- a/packages/adders/supabase/config/adder.ts +++ b/packages/adders/supabase/config/adder.ts @@ -231,7 +231,7 @@ export const adder = defineAdderConfig({ ` ); - addFromRawHtml(htmlAst.childNodes, '{@render children?.()}'); + addFromRawHtml(htmlAst.childNodes, '{@render children()}'); } }, { From 74d4ae6e3ebaa10285e1fb186334bf52435b5ea9 Mon Sep 17 00:00:00 2001 From: AdrianGonz97 <31664583+AdrianGonz97@users.noreply.github.com> Date: Tue, 8 Oct 2024 12:58:32 -0400 Subject: [PATCH 72/76] remove ternaries --- packages/adders/common.ts | 7 ++ packages/adders/supabase/config/adder.ts | 147 +++++++++++------------ packages/adders/supabase/config/demos.ts | 33 +++-- 3 files changed, 94 insertions(+), 93 deletions(-) diff --git a/packages/adders/common.ts b/packages/adders/common.ts index d48635a0..a210e7d5 100644 --- a/packages/adders/common.ts +++ b/packages/adders/common.ts @@ -2,6 +2,13 @@ import { imports, exports, common, variables, functions } from '@svelte-cli/core import { Walker, type AstKinds, type AstTypes, type ScriptFileEditor } from '@svelte-cli/core'; import type { Question } from '@svelte-cli/core/internal'; +export function createPrinter(...conditions: boolean[]) { + const printers = conditions.map((condition) => { + return (content: string, alt = '') => (condition ? content : alt); + }); + return printers; +} + export function addEslintConfigPrettier({ ast }: ScriptFileEditor>) { // if a default import for `eslint-plugin-svelte` already exists, then we'll use their specifier's name instead const importNodes = ast.body.filter((n) => n.type === 'ImportDeclaration'); diff --git a/packages/adders/supabase/config/adder.ts b/packages/adders/supabase/config/adder.ts index 1126506a..bb8ed4b6 100644 --- a/packages/adders/supabase/config/adder.ts +++ b/packages/adders/supabase/config/adder.ts @@ -1,6 +1,6 @@ import { defineAdderConfig, dedent } from '@svelte-cli/core'; import { options as availableOptions } from './options.ts'; -import { addHooksHandle, addGlobalAppInterface, hasTypeProp } from '../../common.ts'; +import { addHooksHandle, addGlobalAppInterface, hasTypeProp, createPrinter } from '../../common.ts'; import { common, imports } from '@svelte-cli/core/js'; import { addFromRawHtml } from '@svelte-cli/core/html'; import { demos } from './demos.ts'; @@ -149,10 +149,11 @@ export const adder = defineAdderConfig({ imports.addNamed(ast, './$types', { LayoutLoad: 'LayoutLoad' }, true); } + const [ts] = createPrinter(typescript); common.addFromString( ast, ` - export const load${typescript ? ': LayoutLoad' : ''} = async ({ data, depends, fetch }) => { + export const load${ts(': LayoutLoad')} = async ({ data, depends, fetch }) => { depends('supabase:auth') const supabase = isBrowser() @@ -192,10 +193,11 @@ export const adder = defineAdderConfig({ imports.addNamed(ast, './$types', { LayoutServerLoad: 'LayoutServerLoad' }, true); } + const [ts] = createPrinter(typescript); common.addFromString( ast, ` - export const load${typescript ? ': LayoutServerLoad' : ''} = async ({ locals: { session }, cookies }) => { + export const load${ts(': LayoutServerLoad')} = async ({ locals: { session }, cookies }) => { return { session, cookies: cookies.getAll(), @@ -243,47 +245,49 @@ export const adder = defineAdderConfig({ content: ({ options, typescript }) => { const isBasic = options.auth.includes('basic'); const isMagicLink = options.auth.includes('magic-link'); + const [ts, demo, basic, magic] = createPrinter( + typescript, + options.demo, + isBasic, + isMagicLink + ); return dedent` - ${isBasic ? `import { redirect } from '@sveltejs/kit'` : ''} - ${options.demo ? `import { PUBLIC_BASE_URL } from '$env/static/public'` : ''} - ${typescript ? `import type { Actions } from './$types'` : ''} + ${basic(`import { redirect } from '@sveltejs/kit'`)} + ${demo(`import { PUBLIC_BASE_URL } from '$env/static/public'`)} + ${ts(`import type { Actions } from './$types'`)} - export const actions${typescript ? ': Actions' : ''} = {${ - isBasic - ? ` + export const actions${ts(': Actions')} = {${basic(` signup: async ({ request, locals: { supabase } }) => { const formData = await request.formData() - const email = formData.get('email')${typescript ? ' as string' : ''} - const password = formData.get('password')${typescript ? ' as string' : ''} + const email = formData.get('email')${ts(' as string')} + const password = formData.get('password')${ts(' as string')} - const { error } = await supabase.auth.signUp({${ - options.demo - ? ` + const { error } = await supabase.auth.signUp({${demo( + ` email, password, options: { emailRedirectTo: \`\${PUBLIC_BASE_URL}/private\`, } - })` - : ' email, password })' - } + })`, + ' email, password })' + )} if (error) { console.error(error) return { message: 'Something went wrong, please try again.' } } else { - ${ - options.demo - ? `// Redirect to local Inbucket for demo purposes - redirect(303, \`http://localhost:54324/m/\${email}\`)` - : `return { message: 'Sign up succeeded! Please check your email inbox.' }` - } + ${demo( + `// Redirect to local Inbucket for demo purposes + redirect(303, \`http://localhost:54324/m/\${email}\`)`, + `return { message: 'Sign up succeeded! Please check your email inbox.' }` + )} } }, login: async ({ request, locals: { supabase } }) => { const formData = await request.formData() - const email = formData.get('email')${typescript ? ' as string' : ''} - const password = formData.get('password')${typescript ? ' as string' : ''} + const email = formData.get('email')${ts(' as string')} + const password = formData.get('password')${ts(' as string')} const { error } = await supabase.auth.signInWithPassword({ email, password }) if (error) { @@ -291,15 +295,11 @@ export const adder = defineAdderConfig({ return { message: 'Something went wrong, please try again.' } } - redirect(303, '${options.demo ? '/private' : '/'}') - },` - : '' - }${ - isMagicLink - ? ` + redirect(303, '${demo('/private', '/')}') + },`)}${magic(` magic: async ({ request, locals: { supabase } }) => { const formData = await request.formData() - const email = formData.get('email')${typescript ? ' as string' : ''} + const email = formData.get('email')${ts(' as string')} const { error } = await supabase.auth.signInWithOtp({ email }) if (error) { @@ -309,9 +309,7 @@ export const adder = defineAdderConfig({ return { message: 'Check your email inbox.' } - },` - : '' - } + },`)} } `; } @@ -324,27 +322,29 @@ export const adder = defineAdderConfig({ const isBasic = options.auth.includes('basic'); const isMagicLink = options.auth.includes('magic-link'); + const [ts, basic, magic, magicOrBasic] = createPrinter( + typescript, + isBasic, + isMagicLink, + isBasic || isMagicLink + ); return dedent` - - ${isBasic || isMagicLink ? `import { enhance } from '$app/forms'` : ''} - ${(isBasic || isMagicLink) && typescript ? `import type { ActionData } from './$types'` : ''} + + ${magicOrBasic(`import { enhance } from '$app/forms'`)} + ${magicOrBasic(ts(`import type { ActionData } from './$types'`))} - ${isBasic || isMagicLink ? `export let form${typescript ? ': ActionData' : ''}` : ''} + ${magicOrBasic(`export let form${ts(': ActionData')}`)} - ${ - isBasic || isMagicLink - ? ` + ${magicOrBasic( + ` ` - : '' - } - ${ - isBasic - ? ` + )} + ${basic(`