0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-03-03 22:57:08 -05:00
This commit is contained in:
Fred K. Schott 2024-01-24 16:07:17 -08:00
parent bfd2706378
commit 65696de832
9 changed files with 49 additions and 48 deletions

View file

@ -25,7 +25,9 @@ export async function getPackage<T>(
// Custom resolution logic for @astrojs/db. Since it lives in our monorepo, // Custom resolution logic for @astrojs/db. Since it lives in our monorepo,
// the generic tryResolve() method doesn't work. // the generic tryResolve() method doesn't work.
if (packageName === '@astrojs/db') { if (packageName === '@astrojs/db') {
const packageJsonLoc = require.resolve(packageName + '/package.json', {paths: [options.cwd ?? process.cwd()]}); const packageJsonLoc = require.resolve(packageName + '/package.json', {
paths: [options.cwd ?? process.cwd()],
});
const packageLoc = packageJsonLoc.replace(`package.json`, 'dist/index.js'); const packageLoc = packageJsonLoc.replace(`package.json`, 'dist/index.js');
const packageImport = await import(packageLoc); const packageImport = await import(packageLoc);
return packageImport as T; return packageImport as T;

View file

@ -1,10 +1,7 @@
import type { AstroConfig } from 'astro'; import type { AstroConfig } from 'astro';
import deepDiff from 'deep-diff'; import deepDiff from 'deep-diff';
import type { Arguments } from 'yargs-parser'; import type { Arguments } from 'yargs-parser';
import { import { getMigrations, initializeFromMigrations } from '../../../migrations.js';
getMigrations,
initializeFromMigrations,
} from '../../../migrations.js';
const { diff, applyChange } = deepDiff; const { diff, applyChange } = deepDiff;
export async function cmd({ config }: { config: AstroConfig; flags: Arguments }) { export async function cmd({ config }: { config: AstroConfig; flags: Arguments }) {

View file

@ -18,7 +18,7 @@ export async function cli({ flags, config }: { flags: Arguments; config: AstroCo
return await verifyCommand({ config, flags }); return await verifyCommand({ config, flags });
} }
default: { default: {
if(command == null) { if (command == null) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error(`No command provided. console.error(`No command provided.
@ -40,6 +40,6 @@ Usage:
astro db sync Creates snapshot based on your schema astro db sync Creates snapshot based on your schema
astro db push Pushes migrations to Astro Studio astro db push Pushes migrations to Astro Studio
astro db verify Verifies migrations have been pushed and errors if not` astro db verify Verifies migrations have been pushed and errors if not`;
} }
} }

View file

@ -41,13 +41,13 @@ export async function getMigrationQueries({
const resolved = await resolveCollectionRenames( const resolved = await resolveCollectionRenames(
added, added,
dropped, dropped,
promptResponses?.collectionRenames, promptResponses?.collectionRenames
); );
added = resolved.added; added = resolved.added;
dropped = resolved.dropped; dropped = resolved.dropped;
for (const { from, to } of resolved.renamed) { for (const { from, to } of resolved.renamed) {
const renameQuery = `ALTER TABLE ${sqlite.escapeName(from)} RENAME TO ${sqlite.escapeName( const renameQuery = `ALTER TABLE ${sqlite.escapeName(from)} RENAME TO ${sqlite.escapeName(
to, to
)}`; )}`;
queries.push(renameQuery); queries.push(renameQuery);
} }
@ -100,7 +100,7 @@ export async function getCollectionChangeQueries({
collectionName, collectionName,
added, added,
dropped, dropped,
promptResponses?.fieldRenames, promptResponses?.fieldRenames
); );
added = resolved.added; added = resolved.added;
dropped = resolved.dropped; dropped = resolved.dropped;
@ -120,26 +120,26 @@ export async function getCollectionChangeQueries({
let allowDataLoss = promptResponses?.allowDataLoss; let allowDataLoss = promptResponses?.allowDataLoss;
const nameMsg = `Type the collection name ${color.blue( const nameMsg = `Type the collection name ${color.blue(
collectionName, collectionName
)} to confirm you want to delete all data:`; )} to confirm you want to delete all data:`;
const { reason, fieldName } = dataLossCheck; const { reason, fieldName } = dataLossCheck;
const reasonMsgs: Record<DataLossReason, string> = { const reasonMsgs: Record<DataLossReason, string> = {
'added-required': `Adding required ${color.blue( 'added-required': `Adding required ${color.blue(
color.bold(collectionName), color.bold(collectionName)
)} field ${color.blue(color.bold(fieldName))}. ${color.red( )} field ${color.blue(color.bold(fieldName))}. ${color.red(
'This will delete all existing data in the collection!', 'This will delete all existing data in the collection!'
)} We recommend setting a default value to avoid data loss.`, )} We recommend setting a default value to avoid data loss.`,
'updated-required': `Changing ${color.blue(color.bold(collectionName))} field ${color.blue( 'updated-required': `Changing ${color.blue(color.bold(collectionName))} field ${color.blue(
color.bold(fieldName), color.bold(fieldName)
)} to required. ${color.red('This will delete all existing data in the collection!')}`, )} to required. ${color.red('This will delete all existing data in the collection!')}`,
'updated-unique': `Changing ${color.blue(color.bold(collectionName))} field ${color.blue( 'updated-unique': `Changing ${color.blue(color.bold(collectionName))} field ${color.blue(
color.bold(fieldName), color.bold(fieldName)
)} to unique. ${color.red('This will delete all existing data in the collection!')}`, )} to unique. ${color.red('This will delete all existing data in the collection!')}`,
'updated-type': `Changing the type of ${color.blue( 'updated-type': `Changing the type of ${color.blue(
color.bold(collectionName), color.bold(collectionName)
)} field ${color.blue(color.bold(fieldName))}. ${color.red( )} field ${color.blue(color.bold(fieldName))}. ${color.red(
'This will delete all existing data in the collection!', 'This will delete all existing data in the collection!'
)}`, )}`,
}; };
@ -175,7 +175,7 @@ async function resolveFieldRenames(
collectionName: string, collectionName: string,
mightAdd: DBFields, mightAdd: DBFields,
mightDrop: DBFields, mightDrop: DBFields,
renamePromptResponses?: PromptResponses['fieldRenames'], renamePromptResponses?: PromptResponses['fieldRenames']
): Promise<{ added: DBFields; dropped: DBFields; renamed: Renamed }> { ): Promise<{ added: DBFields; dropped: DBFields; renamed: Renamed }> {
const added: DBFields = {}; const added: DBFields = {};
const dropped: DBFields = {}; const dropped: DBFields = {};
@ -195,7 +195,7 @@ async function resolveFieldRenames(
type: 'toggle', type: 'toggle',
name: 'isRename', name: 'isRename',
message: `Is the field ${color.blue(color.bold(fieldName))} in collection ${color.blue( message: `Is the field ${color.blue(color.bold(fieldName))} in collection ${color.blue(
color.bold(collectionName), color.bold(collectionName)
)} a new field, or renaming an existing field?`, )} a new field, or renaming an existing field?`,
initial: false, initial: false,
active: 'Rename', active: 'Rename',
@ -215,7 +215,7 @@ async function resolveFieldRenames(
type: 'select', type: 'select',
name: 'oldFieldName', name: 'oldFieldName',
message: `Which field in ${color.blue( message: `Which field in ${color.blue(
color.bold(collectionName), color.bold(collectionName)
)} should be renamed to ${color.blue(color.bold(fieldName))}?`, )} should be renamed to ${color.blue(color.bold(fieldName))}?`,
choices, choices,
}); });
@ -223,7 +223,8 @@ async function resolveFieldRenames(
renamed.push({ from: oldFieldName, to: fieldName }); renamed.push({ from: oldFieldName, to: fieldName });
for (const [droppedFieldName, droppedField] of Object.entries(mightDrop)) { for (const [droppedFieldName, droppedField] of Object.entries(mightDrop)) {
if (!renamed.find((r) => r.from === droppedFieldName)) dropped[droppedFieldName] = droppedField; if (!renamed.find((r) => r.from === droppedFieldName))
dropped[droppedFieldName] = droppedField;
} }
} }
@ -233,7 +234,7 @@ async function resolveFieldRenames(
async function resolveCollectionRenames( async function resolveCollectionRenames(
mightAdd: DBCollections, mightAdd: DBCollections,
mightDrop: DBCollections, mightDrop: DBCollections,
renamePromptResponses?: PromptResponses['fieldRenames'], renamePromptResponses?: PromptResponses['fieldRenames']
): Promise<{ added: DBCollections; dropped: DBCollections; renamed: Renamed }> { ): Promise<{ added: DBCollections; dropped: DBCollections; renamed: Renamed }> {
const added: DBCollections = {}; const added: DBCollections = {};
const dropped: DBCollections = {}; const dropped: DBCollections = {};
@ -253,7 +254,7 @@ async function resolveCollectionRenames(
type: 'toggle', type: 'toggle',
name: 'isRename', name: 'isRename',
message: `Is the collection ${color.blue( message: `Is the collection ${color.blue(
color.bold(collectionName), color.bold(collectionName)
)} a new collection, or renaming an existing collection?`, )} a new collection, or renaming an existing collection?`,
initial: false, initial: false,
active: 'Rename', active: 'Rename',
@ -279,7 +280,8 @@ async function resolveCollectionRenames(
renamed.push({ from: oldCollectionName, to: collectionName }); renamed.push({ from: oldCollectionName, to: collectionName });
for (const [droppedCollectionName, droppedCollection] of Object.entries(mightDrop)) { for (const [droppedCollectionName, droppedCollection] of Object.entries(mightDrop)) {
if (!renamed.find((r) => r.from === droppedCollectionName)) dropped[droppedCollectionName] = droppedCollection; if (!renamed.find((r) => r.from === droppedCollectionName))
dropped[droppedCollectionName] = droppedCollection;
} }
} }
@ -308,7 +310,7 @@ function getFieldRenameQueries(unescCollectionName: string, renamed: Renamed): s
for (const { from, to } of renamed) { for (const { from, to } of renamed) {
const q = `ALTER TABLE ${collectionName} RENAME COLUMN ${sqlite.escapeName( const q = `ALTER TABLE ${collectionName} RENAME COLUMN ${sqlite.escapeName(
from, from
)} TO ${sqlite.escapeName(to)}`; )} TO ${sqlite.escapeName(to)}`;
queries.push(q); queries.push(q);
} }
@ -323,7 +325,7 @@ function getFieldRenameQueries(unescCollectionName: string, renamed: Renamed): s
function getAlterTableQueries( function getAlterTableQueries(
unescCollectionName: string, unescCollectionName: string,
added: DBFields, added: DBFields,
dropped: DBFields, dropped: DBFields
): string[] { ): string[] {
const queries: string[] = []; const queries: string[] = [];
const collectionName = sqlite.escapeName(unescCollectionName); const collectionName = sqlite.escapeName(unescCollectionName);
@ -333,7 +335,7 @@ function getAlterTableQueries(
const type = schemaTypeToSqlType(field.type); const type = schemaTypeToSqlType(field.type);
const q = `ALTER TABLE ${collectionName} ADD COLUMN ${fieldName} ${type}${getModifiers( const q = `ALTER TABLE ${collectionName} ADD COLUMN ${fieldName} ${type}${getModifiers(
fieldName, fieldName,
field, field
)}`; )}`;
queries.push(q); queries.push(q);
} }
@ -369,7 +371,7 @@ function getRecreateTableQueries({
const escapedColumns = originalColumns.map((c) => sqlite.escapeName(c)).join(', '); const escapedColumns = originalColumns.map((c) => sqlite.escapeName(c)).join(', ');
queries.push( queries.push(
`INSERT INTO ${tempName} (${escapedColumns}) SELECT ${escapedColumns} FROM ${collectionName}`, `INSERT INTO ${tempName} (${escapedColumns}) SELECT ${escapedColumns} FROM ${collectionName}`
); );
} }
@ -384,7 +386,7 @@ export function getCreateTableQuery(collectionName: string, collection: DBCollec
const colQueries = ['"id" text PRIMARY KEY']; const colQueries = ['"id" text PRIMARY KEY'];
for (const [columnName, column] of Object.entries(collection.fields)) { for (const [columnName, column] of Object.entries(collection.fields)) {
const colQuery = `${sqlite.escapeName(columnName)} ${schemaTypeToSqlType( const colQuery = `${sqlite.escapeName(columnName)} ${schemaTypeToSqlType(
column.type, column.type
)}${getModifiers(columnName, column)}`; )}${getModifiers(columnName, column)}`;
colQueries.push(colQuery); colQueries.push(colQuery);
} }
@ -448,7 +450,7 @@ type DataLossResponse =
function canRecreateTableWithoutDataLoss( function canRecreateTableWithoutDataLoss(
added: DBFields, added: DBFields,
updated: UpdatedFields, updated: UpdatedFields
): DataLossResponse { ): DataLossResponse {
for (const [fieldName, a] of Object.entries(added)) { for (const [fieldName, a] of Object.entries(added)) {
if (!a.optional && !hasDefault(a)) if (!a.optional && !hasDefault(a))
@ -519,7 +521,7 @@ const typeChangesWithoutQuery: Array<{ from: FieldType; to: FieldType }> = [
function canChangeTypeWithoutQuery(oldField: DBField, newField: DBField) { function canChangeTypeWithoutQuery(oldField: DBField, newField: DBField) {
return typeChangesWithoutQuery.some( return typeChangesWithoutQuery.some(
({ from, to }) => oldField.type === from && newField.type === to, ({ from, to }) => oldField.type === from && newField.type === to
); );
} }
@ -560,8 +562,8 @@ function getDefaultValueSql(columnName: string, column: DBFieldWithDefault): str
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log( console.log(
`Invalid default value for column ${color.bold( `Invalid default value for column ${color.bold(
columnName, columnName
)}. Defaults must be valid JSON when using the \`json()\` type.`, )}. Defaults must be valid JSON when using the \`json()\` type.`
); );
process.exit(0); process.exit(0);
} }

View file

@ -37,7 +37,7 @@ export const errorMap: z.ZodErrorMap = (baseError, ctx) => {
const messages: string[] = [ const messages: string[] = [
prefix( prefix(
baseErrorPath, baseErrorPath,
typeOrLiteralErrByPath.size ? 'Did not match union:' : 'Did not match union.', typeOrLiteralErrByPath.size ? 'Did not match union:' : 'Did not match union.'
), ),
]; ];
return { return {
@ -51,8 +51,8 @@ export const errorMap: z.ZodErrorMap = (baseError, ctx) => {
// Avoid printing the key again if it's a base error // Avoid printing the key again if it's a base error
key === baseErrorPath key === baseErrorPath
? `> ${getTypeOrLiteralMsg(error)}` ? `> ${getTypeOrLiteralMsg(error)}`
: `> ${prefix(key, getTypeOrLiteralMsg(error))}`, : `> ${prefix(key, getTypeOrLiteralMsg(error))}`
), )
) )
.join('\n'), .join('\n'),
}; };
@ -65,7 +65,7 @@ export const errorMap: z.ZodErrorMap = (baseError, ctx) => {
code: baseError.code, code: baseError.code,
received: (baseError as any).received, received: (baseError as any).received,
expected: [baseError.expected], expected: [baseError.expected],
}), })
), ),
}; };
} else if (baseError.message) { } else if (baseError.message) {
@ -81,11 +81,11 @@ const getTypeOrLiteralMsg = (error: TypeOrLiteralErrByPathEntry): string => {
switch (error.code) { switch (error.code) {
case 'invalid_type': case 'invalid_type':
return `Expected type \`${unionExpectedVals(expectedDeduped)}\`, received ${JSON.stringify( return `Expected type \`${unionExpectedVals(expectedDeduped)}\`, received ${JSON.stringify(
error.received, error.received
)}`; )}`;
case 'invalid_literal': case 'invalid_literal':
return `Expected \`${unionExpectedVals(expectedDeduped)}\`, received ${JSON.stringify( return `Expected \`${unionExpectedVals(expectedDeduped)}\`, received ${JSON.stringify(
error.received, error.received
)}`; )}`;
} }
}; };

View file

@ -1,11 +1,11 @@
import { red } from 'kleur/colors'; import { red } from 'kleur/colors';
export const unexpectedAstroAdminError = `${red( export const unexpectedAstroAdminError = `${red(
'Unexpected response from Astro Studio servers.', 'Unexpected response from Astro Studio servers.'
)} Try updating your package version. If the problem persists, please contact support.`; )} Try updating your package version. If the problem persists, please contact support.`;
export const authenticationError = `${red( export const authenticationError = `${red(
'⚠️ Login session invalid or expired.', '⚠️ Login session invalid or expired.'
)} Please run \`astro login\` again.`; )} Please run \`astro login\` again.`;
export const appTokenError = `${red( export const appTokenError = `${red(
'⚠️ App token invalid or expired.', '⚠️ App token invalid or expired.'
)} Please generate a new one from your the Studio dashboard under project settings.`; )} Please generate a new one from your the Studio dashboard under project settings.`;

View file

@ -26,7 +26,9 @@ export async function initializeMigrationsDirectory(currentSnapshot: unknown) {
await writeFile('./migrations/0000_snapshot.json', JSON.stringify(currentSnapshot, undefined, 2)); await writeFile('./migrations/0000_snapshot.json', JSON.stringify(currentSnapshot, undefined, 2));
} }
export async function initializeFromMigrations(allMigrationFiles: string[]): Promise<DBCollections> { export async function initializeFromMigrations(
allMigrationFiles: string[]
): Promise<DBCollections> {
const prevSnapshot = await loadInitialSnapshot(); const prevSnapshot = await loadInitialSnapshot();
for (const migration of allMigrationFiles) { for (const migration of allMigrationFiles) {
if (migration === '0000_snapshot.json') continue; if (migration === '0000_snapshot.json') continue;

View file

@ -1,11 +1,10 @@
import type { InStatement } from "@libsql/client"; import type { InStatement } from '@libsql/client';
import type { AstroConfig } from 'astro'; import type { AstroConfig } from 'astro';
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'; import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
import { drizzle } from 'drizzle-orm/sqlite-proxy'; import { drizzle } from 'drizzle-orm/sqlite-proxy';
import { loadEnv } from 'vite'; import { loadEnv } from 'vite';
import { z } from 'zod'; import { z } from 'zod';
export type VitePlugin = Required<AstroConfig['vite']>['plugins'][number]; export type VitePlugin = Required<AstroConfig['vite']>['plugins'][number];
export const STUDIO_ADMIN_TABLE = 'ReservedAstroStudioAdmin'; export const STUDIO_ADMIN_TABLE = 'ReservedAstroStudioAdmin';
@ -16,7 +15,6 @@ export const adminTable = sqliteTable(STUDIO_ADMIN_TABLE, {
collections: text('collections').notNull(), collections: text('collections').notNull(),
}); });
export const STUDIO_MIGRATIONS_TABLE = 'ReservedAstroStudioMigrations'; export const STUDIO_MIGRATIONS_TABLE = 'ReservedAstroStudioMigrations';
export const migrationsTable = sqliteTable(STUDIO_MIGRATIONS_TABLE, { export const migrationsTable = sqliteTable(STUDIO_MIGRATIONS_TABLE, {

View file

@ -9,7 +9,7 @@ describe('astro:db', () => {
fixture = await loadFixture({ fixture = await loadFixture({
root: new URL('./fixtures/basics/', import.meta.url), root: new URL('./fixtures/basics/', import.meta.url),
output: 'server', output: 'server',
adapter: testAdapter() adapter: testAdapter(),
}); });
}); });