Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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(cli): 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