Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/bright-flies-argue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'sv': patch
---

fix(drizzle): `--cwd` option in `add` command is now taken into account
5 changes: 5 additions & 0 deletions .changeset/great-mammals-buy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'sv': patch
---

feat(drizzle): Docker Compose file is now stored in `compose.yaml` instead of `docker-compose.yml`
62 changes: 43 additions & 19 deletions packages/addons/_tests/drizzle/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import drizzle from '../../drizzle/index.ts';
import { pageServer, pageComp } from './fixtures.ts';

// only linux is supported for running docker containers in github runners
const noDocker = process.env.CI && process.platform !== 'linux';
const MUST_HAVE_DOCKER = process.env.CI && process.platform === 'linux';
let dockerInstalled = false;

const { test, testCases, prepareServer } = setupTest(
{ drizzle },
Expand Down Expand Up @@ -38,44 +39,67 @@ const { test, testCases, prepareServer } = setupTest(
);

beforeAll(() => {
if (noDocker) return;
if (!MUST_HAVE_DOCKER) return;
const cwd = path.dirname(fileURLToPath(import.meta.url));
execSync('docker compose up --detach', { cwd, stdio: 'pipe' });

try {
execSync('docker --version', { cwd, stdio: 'pipe' });
dockerInstalled = true;
} catch {
dockerInstalled = false;
}

if (dockerInstalled) execSync('docker compose up --detach', { cwd, stdio: 'pipe' });

// cleans up the containers on interrupts (ctrl+c)
process.addListener('SIGINT', () => {
execSync('docker compose down --volumes', { cwd, stdio: 'pipe' });
if (dockerInstalled) execSync('docker compose down --volumes', { cwd, stdio: 'pipe' });
});

return () => {
execSync('docker compose down --volumes', { cwd, stdio: 'pipe' });
if (dockerInstalled) execSync('docker compose down --volumes', { cwd, stdio: 'pipe' });
};
});

test.concurrent.for(testCases)(
'drizzle $kind.type $variant',
async (testCase, { page, ...ctx }) => {
if (testCase.kind.options.drizzle.docker && noDocker) ctx.skip();
const cwd = ctx.cwd(testCase);

const ts = testCase.variant === 'kit-ts';
const ts = testCase.variant.endsWith('ts');
const drizzleConfig = path.resolve(cwd, `drizzle.config.${ts ? 'ts' : 'js'}`);
const content = fs.readFileSync(drizzleConfig, 'utf8').replace(/strict: true[,\s]/, '');
fs.writeFileSync(drizzleConfig, content, 'utf8');
const content = fs.readFileSync(drizzleConfig, 'utf8');

expect(content.length, 'drizzle config should have content').toBeGreaterThan(0);

if (MUST_HAVE_DOCKER) expect(dockerInstalled, 'docker must be installed').toBe(true);

if (testCase.kind.options.drizzle.docker) {
const dockerCompose = path.resolve(cwd, 'compose.yaml');
expect(fs.existsSync(dockerCompose), 'file should exist').toBe(true);
}

const db_can_be_tested =
!testCase.kind.options.drizzle.docker ||
(testCase.kind.options.drizzle.docker && dockerInstalled);

if (db_can_be_tested) {
fs.writeFileSync(drizzleConfig, content.replace(/strict: true[,\s]/, ''), 'utf8');

const routes = path.resolve(cwd, 'src', 'routes');
const pagePath = path.resolve(routes, '+page.svelte');
fs.writeFileSync(pagePath, pageComp, 'utf8');
const routes = path.resolve(cwd, 'src', 'routes');
const pagePath = path.resolve(routes, '+page.svelte');
fs.writeFileSync(pagePath, pageComp, 'utf8');

const pageServerPath = path.resolve(routes, `+page.server.${ts ? 'ts' : 'js'}`);
fs.writeFileSync(pageServerPath, pageServer, 'utf8');
const pageServerPath = path.resolve(routes, `+page.server.${ts ? 'ts' : 'js'}`);
fs.writeFileSync(pageServerPath, pageServer, 'utf8');

execSync('npm run db:push', { cwd, stdio: 'pipe' });
execSync('npm run db:push', { cwd, stdio: 'pipe' });

const { close } = await prepareServer({ cwd, page });
// kill server process when we're done
ctx.onTestFinished(async () => await close());
const { close } = await prepareServer({ cwd, page });
// kill server process when we're done
ctx.onTestFinished(async () => await close());

expect(page.locator('[data-testid]')).toBeTruthy();
expect(page.locator('[data-testid]')).toBeTruthy();
}
}
);
42 changes: 24 additions & 18 deletions packages/addons/drizzle/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,30 +73,28 @@ export default defineAddon({
shortDescription: 'database orm',
homepage: 'https://orm.drizzle.team',
options,
setup: ({ kit, unsupported, runsAfter, cwd, typescript }) => {
setup: ({ kit, unsupported, runsAfter }) => {
runsAfter('prettier');

const ext = typescript ? 'ts' : 'js';
if (!kit) {
return unsupported('Requires SvelteKit');
}
if (!kit) return unsupported('Requires SvelteKit');
},
run: ({ sv, typescript, options, kit, dependencyVersion, cwd, cancel }) => {
if (!kit) throw new Error('SvelteKit is required');

const baseDBPath = path.resolve(kit.libDirectory, 'server', 'db');
const ext = typescript ? 'ts' : 'js';
const baseDBPath = path.resolve(cwd, kit.libDirectory, 'server', 'db');
const paths = {
'drizzle config': path.relative(cwd, path.resolve(cwd, `drizzle.config.${ext}`)),
'database schema': path.relative(cwd, path.resolve(baseDBPath, `schema.${ext}`)),
database: path.relative(cwd, path.resolve(baseDBPath, `index.${ext}`))
'drizzle config': path.resolve(cwd, `drizzle.config.${ext}`),
'database schema': path.resolve(baseDBPath, `schema.${ext}`),
database: path.resolve(baseDBPath, `index.${ext}`)
};

for (const [fileType, filePath] of Object.entries(paths)) {
if (fs.existsSync(filePath)) {
unsupported(`Preexisting ${fileType} file at '${filePath}'`);
return cancel(`Preexisting ${fileType} file at '${filePath}'`);
}
}
},
run: ({ sv, typescript, options, kit, dependencyVersion }) => {
const ext = typescript ? 'ts' : 'js';

console.log(`no preexisting files`);
sv.devDependency('drizzle-orm', '^0.44.5');
sv.devDependency('drizzle-kit', '^0.31.4');
sv.devDependency('@types/node', getNodeTypesVersion());
Expand All @@ -123,7 +121,15 @@ export default defineAddon({
sv.file('.env.example', (content) => generateEnvFileContent(content, options));

if (options.docker && (options.mysql === 'mysql2' || options.postgresql === 'postgres.js')) {
sv.file('docker-compose.yml', (content) => {
const composeFileOptions = ['docker-compose.yml', 'docker-compose.yaml', 'compose.yaml'];
let composeFile = '';
for (const option of composeFileOptions) {
composeFile = option;
if (fs.existsSync(path.resolve(cwd, option))) break;
}
if (composeFile === '') throw new Error('unreachable state...');

sv.file(composeFile, (content) => {
// if the file already exists, don't modify it
// (in the future, we could add some tooling for modifying yaml)
if (content.length > 0) return content;
Expand Down Expand Up @@ -206,7 +212,7 @@ export default defineAddon({
});
}

sv.file(`drizzle.config.${ext}`, (content) => {
sv.file(paths['drizzle config'], (content) => {
const { ast, generateCode } = parseScript(content);

imports.addNamed(ast, { from: 'drizzle-kit', imports: { defineConfig: 'defineConfig' } });
Expand Down Expand Up @@ -234,7 +240,7 @@ export default defineAddon({
return generateCode();
});

sv.file(`${kit?.libDirectory}/server/db/schema.${ext}`, (content) => {
sv.file(paths['database schema'], (content) => {
const { ast, generateCode } = parseScript(content);

let userSchemaExpression;
Expand Down Expand Up @@ -286,7 +292,7 @@ export default defineAddon({
return generateCode();
});

sv.file(`${kit?.libDirectory}/server/db/index.${ext}`, (content) => {
sv.file(paths['database'], (content) => {
const { ast, generateCode } = parseScript(content);

imports.addNamed(ast, {
Expand Down
24 changes: 20 additions & 4 deletions packages/cli/commands/add/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ export async function runAddCommand(
options: Options,
selectedAddonIds: string[]
): Promise<{ nextSteps: string[]; packageManager?: AgentName | null }> {
const selectedAddons: SelectedAddon[] = selectedAddonIds.map((id) => ({
let selectedAddons: SelectedAddon[] = selectedAddonIds.map((id) => ({
type: 'official',
addon: getAddonDetails(id)
}));
Expand Down Expand Up @@ -542,14 +542,30 @@ export async function runAddCommand(
const details = officialDetails.concat(commDetails);

const addonMap: AddonMap = Object.assign({}, ...details.map((a) => ({ [a.id]: a })));
const { filesToFormat, pnpmBuildDependencies: addonPnpmBuildDependencies } = await applyAddons({
const { filesToFormat, pnpmBuildDependencies, status } = await applyAddons({
workspace,
addonSetupResults,
addons: addonMap,
options: official
});

p.log.success('Successfully setup add-ons');
const addonSuccess: string[] = [];
for (const [addonId, info] of Object.entries(status)) {
if (info === 'success') addonSuccess.push(addonId);
else {
p.log.warn(`Canceled ${addonId}: ${info.join(', ')}`);
selectedAddons = selectedAddons.filter((a) => a.addon.id !== addonId);
}
}

if (addonSuccess.length === 0) {
p.cancel('All selected add-ons were canceled.');
process.exit(1);
} else if (addonSuccess.length === Object.entries(status).length) {
p.log.success('Successfully setup add-ons');
} else {
p.log.success(`Successfully setup: ${addonSuccess.join(', ')}`);
}

// prompt for package manager and install dependencies
let packageManager: PackageManager | undefined;
Expand All @@ -562,7 +578,7 @@ export async function runAddCommand(

await addPnpmBuildDependencies(workspace.cwd, packageManager, [
'esbuild',
...addonPnpmBuildDependencies
...pnpmBuildDependencies
]);

await installDependencies(packageManager, options.cwd);
Expand Down
31 changes: 25 additions & 6 deletions packages/cli/lib/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,29 +54,37 @@ export async function applyAddons({
}: ApplyAddonOptions): Promise<{
filesToFormat: string[];
pnpmBuildDependencies: string[];
status: Record<string, string[] | 'success'>;
}> {
const filesToFormat = new Set<string>();
const allPnpmBuildDependencies: string[] = [];
const status: Record<string, string[] | 'success'> = {};

const mapped = Object.entries(addons).map(([, addon]) => addon);
const ordered = orderAddons(mapped, addonSetupResults);

for (const addon of ordered) {
workspace = await createWorkspace({ ...workspace, options: options[addon.id] });

const { files, pnpmBuildDependencies } = await runAddon({
const { files, pnpmBuildDependencies, cancels } = await runAddon({
workspace,
addon,
multiple: ordered.length > 1
});

files.forEach((f) => filesToFormat.add(f));
pnpmBuildDependencies.forEach((s) => allPnpmBuildDependencies.push(s));
if (cancels.length === 0) {
status[addon.id] = 'success';
} else {
status[addon.id] = cancels;
}
}

return {
filesToFormat: Array.from(filesToFormat),
pnpmBuildDependencies: allPnpmBuildDependencies
pnpmBuildDependencies: allPnpmBuildDependencies,
status
};
}

Expand Down Expand Up @@ -177,14 +185,25 @@ async function runAddon({ addon, multiple, workspace }: RunAddon) {
pnpmBuildDependencies.push(pkg);
}
};
await addon.run({ ...workspace, sv });

const pkgPath = installPackages(dependencies, workspace);
files.add(pkgPath);
const cancels: string[] = [];
await addon.run({
cancel: (reason) => {
cancels.push(reason);
},
...workspace,
sv
});

if (cancels.length === 0) {
const pkgPath = installPackages(dependencies, workspace);
files.add(pkgPath);
}

return {
files: Array.from(files),
pnpmBuildDependencies
pnpmBuildDependencies,
cancels
};
}

Expand Down
4 changes: 3 additions & 1 deletion packages/core/addon/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ export type Addon<Args extends OptionDefinition> = {
runsAfter: (addonName: string) => void;
}
) => MaybePromise<void>;
run: (workspace: Workspace<Args> & { sv: SvApi }) => MaybePromise<void>;
run: (
workspace: Workspace<Args> & { sv: SvApi; cancel: (reason: string) => void }
) => MaybePromise<void>;
nextSteps?: (
data: {
highlighter: Highlighter;
Expand Down
Loading