mirror of
https://github.com/withastro/astro.git
synced 2025-03-10 23:01:26 -05:00
fix: add back promptResponse injection
This commit is contained in:
parent
76721dd193
commit
db874e508e
2 changed files with 128 additions and 113 deletions
|
@ -29,19 +29,29 @@ import { hasPrimaryKey } from '../../runtime/index.js';
|
|||
const sqlite = new SQLiteAsyncDialect();
|
||||
const genTempTableName = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10);
|
||||
|
||||
/** Dependency injected for unit testing */
|
||||
type AmbiguityResponses = {
|
||||
collectionRenames: Record<string, string>;
|
||||
fieldRenames: {
|
||||
[collectionName: string]: Record<string, string>;
|
||||
};
|
||||
};
|
||||
|
||||
export async function getMigrationQueries({
|
||||
oldSnapshot,
|
||||
newSnapshot,
|
||||
ambiguityResponses,
|
||||
}: {
|
||||
oldSnapshot: DBSnapshot;
|
||||
newSnapshot: DBSnapshot;
|
||||
ambiguityResponses?: AmbiguityResponses;
|
||||
}): Promise<{ queries: string[]; confirmations: string[] }> {
|
||||
const queries: string[] = [];
|
||||
const confirmations: string[] = [];
|
||||
let added = getAddedCollections(oldSnapshot, newSnapshot);
|
||||
let dropped = getDroppedCollections(oldSnapshot, newSnapshot);
|
||||
if (!isEmpty(added) && !isEmpty(dropped)) {
|
||||
const resolved = await resolveCollectionRenames(added, dropped);
|
||||
const resolved = await resolveCollectionRenames(added, dropped, ambiguityResponses);
|
||||
added = resolved.added;
|
||||
dropped = resolved.dropped;
|
||||
for (const { from, to } of resolved.renamed) {
|
||||
|
@ -80,10 +90,12 @@ export async function getCollectionChangeQueries({
|
|||
collectionName,
|
||||
oldCollection,
|
||||
newCollection,
|
||||
ambiguityResponses,
|
||||
}: {
|
||||
collectionName: string;
|
||||
oldCollection: DBCollection;
|
||||
newCollection: DBCollection;
|
||||
ambiguityResponses?: AmbiguityResponses;
|
||||
}): Promise<{ queries: string[]; confirmations: string[] }> {
|
||||
const queries: string[] = [];
|
||||
const confirmations: string[] = [];
|
||||
|
@ -102,7 +114,7 @@ export async function getCollectionChangeQueries({
|
|||
};
|
||||
}
|
||||
if (!isEmpty(added) && !isEmpty(dropped)) {
|
||||
const resolved = await resolveFieldRenames(collectionName, added, dropped);
|
||||
const resolved = await resolveFieldRenames(collectionName, added, dropped, ambiguityResponses);
|
||||
added = resolved.added;
|
||||
dropped = resolved.dropped;
|
||||
queries.push(...getFieldRenameQueries(collectionName, resolved.renamed));
|
||||
|
@ -127,9 +139,21 @@ export async function getCollectionChangeQueries({
|
|||
if (dataLossCheck.dataLoss) {
|
||||
const { reason, fieldName } = dataLossCheck;
|
||||
const reasonMsgs: Record<DataLossReason, string> = {
|
||||
'added-required': `New field ${color.bold(collectionName + '.' + fieldName)} is required with no default value.\nThis requires deleting existing data in the ${color.bold(collectionName)} collection.`,
|
||||
'added-unique': `New field ${color.bold(collectionName + '.' + fieldName)} is marked as unique.\nThis requires deleting existing data in the ${color.bold(collectionName)} collection.`,
|
||||
'updated-type': `Updated field ${color.bold(collectionName + '.' + fieldName)} cannot convert data to new field data type.\nThis requires deleting existing data in the ${color.bold(collectionName)} collection.`,
|
||||
'added-required': `New field ${color.bold(
|
||||
collectionName + '.' + fieldName
|
||||
)} is required with no default value.\nThis requires deleting existing data in the ${color.bold(
|
||||
collectionName
|
||||
)} collection.`,
|
||||
'added-unique': `New field ${color.bold(
|
||||
collectionName + '.' + fieldName
|
||||
)} is marked as unique.\nThis requires deleting existing data in the ${color.bold(
|
||||
collectionName
|
||||
)} collection.`,
|
||||
'updated-type': `Updated field ${color.bold(
|
||||
collectionName + '.' + fieldName
|
||||
)} cannot convert data to new field data type.\nThis requires deleting existing data in the ${color.bold(
|
||||
collectionName
|
||||
)} collection.`,
|
||||
};
|
||||
confirmations.push(reasonMsgs[reason]);
|
||||
}
|
||||
|
@ -180,35 +204,42 @@ type Renamed = Array<{ from: string; to: string }>;
|
|||
async function resolveFieldRenames(
|
||||
collectionName: string,
|
||||
mightAdd: DBFields,
|
||||
mightDrop: DBFields
|
||||
mightDrop: DBFields,
|
||||
ambiguityResponses?: AmbiguityResponses
|
||||
): Promise<{ added: DBFields; dropped: DBFields; renamed: Renamed }> {
|
||||
const added: DBFields = {};
|
||||
const dropped: DBFields = {};
|
||||
const renamed: Renamed = [];
|
||||
|
||||
for (const [fieldName, field] of Object.entries(mightAdd)) {
|
||||
const { oldFieldName } = (await prompts(
|
||||
{
|
||||
type: 'select',
|
||||
name: 'oldFieldName',
|
||||
message:
|
||||
'New field ' +
|
||||
color.blue(color.bold(`${collectionName}.${fieldName}`)) +
|
||||
' detected. Was this renamed from an existing field?',
|
||||
choices: [
|
||||
{ title: 'New field (not renamed from existing)', value: '__NEW__' },
|
||||
...Object.keys(mightDrop)
|
||||
.filter((key) => !(key in renamed))
|
||||
.map((key) => ({ title: key, value: key })),
|
||||
],
|
||||
},
|
||||
{
|
||||
onCancel: () => {
|
||||
process.exit(1);
|
||||
let oldFieldName = ambiguityResponses
|
||||
? ambiguityResponses.fieldRenames[collectionName]?.[fieldName] ?? '__NEW__'
|
||||
: undefined;
|
||||
if (!oldFieldName) {
|
||||
const res = await prompts(
|
||||
{
|
||||
type: 'select',
|
||||
name: 'fieldName',
|
||||
message:
|
||||
'New field ' +
|
||||
color.blue(color.bold(`${collectionName}.${fieldName}`)) +
|
||||
' detected. Was this renamed from an existing field?',
|
||||
choices: [
|
||||
{ title: 'New field (not renamed from existing)', value: '__NEW__' },
|
||||
...Object.keys(mightDrop)
|
||||
.filter((key) => !(key in renamed))
|
||||
.map((key) => ({ title: key, value: key })),
|
||||
],
|
||||
},
|
||||
}
|
||||
)) as { oldFieldName: string };
|
||||
// Handle their response
|
||||
{
|
||||
onCancel: () => {
|
||||
process.exit(1);
|
||||
},
|
||||
}
|
||||
);
|
||||
oldFieldName = res.fieldName as string;
|
||||
}
|
||||
|
||||
if (oldFieldName === '__NEW__') {
|
||||
added[fieldName] = field;
|
||||
} else {
|
||||
|
@ -226,35 +257,42 @@ async function resolveFieldRenames(
|
|||
|
||||
async function resolveCollectionRenames(
|
||||
mightAdd: DBCollections,
|
||||
mightDrop: DBCollections
|
||||
mightDrop: DBCollections,
|
||||
ambiguityResponses?: AmbiguityResponses
|
||||
): Promise<{ added: DBCollections; dropped: DBCollections; renamed: Renamed }> {
|
||||
const added: DBCollections = {};
|
||||
const dropped: DBCollections = {};
|
||||
const renamed: Renamed = [];
|
||||
|
||||
for (const [collectionName, collection] of Object.entries(mightAdd)) {
|
||||
const { oldCollectionName } = (await prompts(
|
||||
{
|
||||
type: 'select',
|
||||
name: 'oldCollectionName',
|
||||
message:
|
||||
'New collection ' +
|
||||
color.blue(color.bold(collectionName)) +
|
||||
' detected. Was this renamed from an existing collection?',
|
||||
choices: [
|
||||
{ title: 'New collection (not renamed from existing)', value: '__NEW__' },
|
||||
...Object.keys(mightDrop)
|
||||
.filter((key) => !(key in renamed))
|
||||
.map((key) => ({ title: key, value: key })),
|
||||
],
|
||||
},
|
||||
{
|
||||
onCancel: () => {
|
||||
process.exit(1);
|
||||
let oldCollectionName = ambiguityResponses
|
||||
? ambiguityResponses.collectionRenames[collectionName] ?? '__NEW__'
|
||||
: undefined;
|
||||
if (!oldCollectionName) {
|
||||
const res = await prompts(
|
||||
{
|
||||
type: 'select',
|
||||
name: 'collectionName',
|
||||
message:
|
||||
'New collection ' +
|
||||
color.blue(color.bold(collectionName)) +
|
||||
' detected. Was this renamed from an existing collection?',
|
||||
choices: [
|
||||
{ title: 'New collection (not renamed from existing)', value: '__NEW__' },
|
||||
...Object.keys(mightDrop)
|
||||
.filter((key) => !(key in renamed))
|
||||
.map((key) => ({ title: key, value: key })),
|
||||
],
|
||||
},
|
||||
}
|
||||
)) as { oldCollectionName: string };
|
||||
// Handle their response
|
||||
{
|
||||
onCancel: () => {
|
||||
process.exit(1);
|
||||
},
|
||||
}
|
||||
);
|
||||
oldCollectionName = res.collectionName as string;
|
||||
}
|
||||
|
||||
if (oldCollectionName === '__NEW__') {
|
||||
added[collectionName] = collection;
|
||||
} else {
|
||||
|
|
|
@ -5,54 +5,46 @@ import {
|
|||
getMigrationQueries,
|
||||
} from '../../dist/core/cli/migration-queries.js';
|
||||
import { getCreateTableQuery } from '../../dist/core/queries.js';
|
||||
import { field, collectionSchema } from '../../dist/core/types.js';
|
||||
import { field, defineCollection } from '../../dist/core/types.js';
|
||||
|
||||
const COLLECTION_NAME = 'Users';
|
||||
|
||||
const userInitial = collectionSchema.parse({
|
||||
const userInitial = defineCollection({
|
||||
fields: {
|
||||
name: field.text(),
|
||||
age: field.number(),
|
||||
email: field.text({ unique: true }),
|
||||
mi: field.text({ optional: true }),
|
||||
},
|
||||
writable: false,
|
||||
});
|
||||
|
||||
const defaultPromptResponse = {
|
||||
allowDataLoss: false,
|
||||
fieldRenames: new Proxy(
|
||||
{},
|
||||
{
|
||||
get: () => false,
|
||||
}
|
||||
),
|
||||
collectionRenames: new Proxy(
|
||||
{},
|
||||
{
|
||||
get: () => false,
|
||||
}
|
||||
),
|
||||
const defaultAmbiguityResponses = {
|
||||
collectionRenames: {},
|
||||
fieldRenames: {},
|
||||
};
|
||||
|
||||
function userChangeQueries(oldCollection, newCollection, promptResponses = defaultPromptResponse) {
|
||||
function userChangeQueries(
|
||||
oldCollection,
|
||||
newCollection,
|
||||
ambiguityResponses = defaultAmbiguityResponses
|
||||
) {
|
||||
return getCollectionChangeQueries({
|
||||
collectionName: COLLECTION_NAME,
|
||||
oldCollection,
|
||||
newCollection,
|
||||
promptResponses,
|
||||
ambiguityResponses,
|
||||
});
|
||||
}
|
||||
|
||||
function configChangeQueries(
|
||||
oldCollections,
|
||||
newCollections,
|
||||
promptResponses = defaultPromptResponse
|
||||
ambiguityResponses = defaultAmbiguityResponses
|
||||
) {
|
||||
return getMigrationQueries({
|
||||
oldSnapshot: { schema: oldCollections, experimentalVersion: 1 },
|
||||
newSnapshot: { schema: newCollections, experimentalVersion: 1 },
|
||||
promptResponses,
|
||||
ambiguityResponses,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -61,21 +53,21 @@ describe('field queries', () => {
|
|||
it('should be empty when collections are the same', async () => {
|
||||
const oldCollections = { [COLLECTION_NAME]: userInitial };
|
||||
const newCollections = { [COLLECTION_NAME]: userInitial };
|
||||
const queries = await configChangeQueries(oldCollections, newCollections);
|
||||
const { queries } = await configChangeQueries(oldCollections, newCollections);
|
||||
expect(queries).to.deep.equal([]);
|
||||
});
|
||||
|
||||
it('should create table for new collections', async () => {
|
||||
const oldCollections = {};
|
||||
const newCollections = { [COLLECTION_NAME]: userInitial };
|
||||
const queries = await configChangeQueries(oldCollections, newCollections);
|
||||
const { queries } = await configChangeQueries(oldCollections, newCollections);
|
||||
expect(queries).to.deep.equal([getCreateTableQuery(COLLECTION_NAME, userInitial)]);
|
||||
});
|
||||
|
||||
it('should drop table for removed collections', async () => {
|
||||
const oldCollections = { [COLLECTION_NAME]: userInitial };
|
||||
const newCollections = {};
|
||||
const queries = await configChangeQueries(oldCollections, newCollections);
|
||||
const { queries } = await configChangeQueries(oldCollections, newCollections);
|
||||
expect(queries).to.deep.equal([`DROP TABLE "${COLLECTION_NAME}"`]);
|
||||
});
|
||||
|
||||
|
@ -83,8 +75,8 @@ describe('field queries', () => {
|
|||
const rename = 'Peeps';
|
||||
const oldCollections = { [COLLECTION_NAME]: userInitial };
|
||||
const newCollections = { [rename]: userInitial };
|
||||
const queries = await configChangeQueries(oldCollections, newCollections, {
|
||||
...defaultPromptResponse,
|
||||
const { queries } = await configChangeQueries(oldCollections, newCollections, {
|
||||
...defaultAmbiguityResponses,
|
||||
collectionRenames: { [rename]: COLLECTION_NAME },
|
||||
});
|
||||
expect(queries).to.deep.equal([`ALTER TABLE "${COLLECTION_NAME}" RENAME TO "${rename}"`]);
|
||||
|
@ -93,26 +85,26 @@ describe('field queries', () => {
|
|||
|
||||
describe('getCollectionChangeQueries', () => {
|
||||
it('should be empty when collections are the same', async () => {
|
||||
const queries = await userChangeQueries(userInitial, userInitial);
|
||||
const { queries } = await userChangeQueries(userInitial, userInitial);
|
||||
expect(queries).to.deep.equal([]);
|
||||
});
|
||||
|
||||
it('should be empty when type updated to same underlying SQL type', async () => {
|
||||
const blogInitial = collectionSchema.parse({
|
||||
const blogInitial = defineCollection({
|
||||
...userInitial,
|
||||
fields: {
|
||||
title: field.text(),
|
||||
draft: field.boolean(),
|
||||
},
|
||||
});
|
||||
const blogFinal = collectionSchema.parse({
|
||||
const blogFinal = defineCollection({
|
||||
...userInitial,
|
||||
fields: {
|
||||
...blogInitial.fields,
|
||||
draft: field.number(),
|
||||
},
|
||||
});
|
||||
const queries = await userChangeQueries(blogInitial, blogFinal);
|
||||
const { queries } = await userChangeQueries(blogInitial, blogFinal);
|
||||
expect(queries).to.deep.equal([]);
|
||||
});
|
||||
|
||||
|
@ -127,9 +119,9 @@ describe('field queries', () => {
|
|||
userFinal.fields.middleInitial = userFinal.fields.mi;
|
||||
delete userFinal.fields.mi;
|
||||
|
||||
const queries = await userChangeQueries(userInitial, userFinal, {
|
||||
...defaultPromptResponse,
|
||||
fieldRenames: { middleInitial: 'mi' },
|
||||
const { queries } = await userChangeQueries(userInitial, userFinal, {
|
||||
collectionRenames: {},
|
||||
fieldRenames: { [COLLECTION_NAME]: { middleInitial: 'mi' } },
|
||||
});
|
||||
expect(queries).to.deep.equal([
|
||||
`ALTER TABLE "${COLLECTION_NAME}" RENAME COLUMN "mi" TO "middleInitial"`,
|
||||
|
@ -147,10 +139,7 @@ describe('field queries', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const queries = await userChangeQueries(userInitial, userFinal, {
|
||||
...defaultPromptResponse,
|
||||
allowDataLoss: true,
|
||||
});
|
||||
const { queries } = await userChangeQueries(userInitial, userFinal);
|
||||
|
||||
expect(queries).to.deep.equal([
|
||||
'DROP TABLE "Users"',
|
||||
|
@ -167,10 +156,7 @@ describe('field queries', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const queries = await userChangeQueries(userInitial, userFinal, {
|
||||
...defaultPromptResponse,
|
||||
allowDataLoss: true,
|
||||
});
|
||||
const { queries } = await userChangeQueries(userInitial, userFinal);
|
||||
|
||||
expect(queries).to.deep.equal([
|
||||
'DROP TABLE "Users"',
|
||||
|
@ -189,10 +175,7 @@ describe('field queries', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const queries = await userChangeQueries(userInitial, userFinal, {
|
||||
...defaultPromptResponse,
|
||||
allowDataLoss: true,
|
||||
});
|
||||
const { queries } = await userChangeQueries(userInitial, userFinal);
|
||||
expect(queries).to.have.lengthOf(4);
|
||||
|
||||
const tempTableName = getTempTableName(queries[0]);
|
||||
|
@ -214,7 +197,7 @@ describe('field queries', () => {
|
|||
};
|
||||
delete userFinal.fields.email;
|
||||
|
||||
const queries = await userChangeQueries(userInitial, userFinal);
|
||||
const { queries } = await userChangeQueries(userInitial, userFinal);
|
||||
expect(queries).to.have.lengthOf(4);
|
||||
|
||||
const tempTableName = getTempTableName(queries[0]);
|
||||
|
@ -228,7 +211,7 @@ describe('field queries', () => {
|
|||
});
|
||||
|
||||
it('when updating to a runtime default', async () => {
|
||||
const initial = collectionSchema.parse({
|
||||
const initial = defineCollection({
|
||||
...userInitial,
|
||||
fields: {
|
||||
...userInitial.fields,
|
||||
|
@ -236,15 +219,15 @@ describe('field queries', () => {
|
|||
},
|
||||
});
|
||||
|
||||
const userFinal = {
|
||||
...userInitial,
|
||||
const userFinal = defineCollection({
|
||||
...initial,
|
||||
fields: {
|
||||
...initial.fields,
|
||||
age: field.date({ default: 'now' }),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const queries = await userChangeQueries(initial, userFinal);
|
||||
const { queries } = await userChangeQueries(initial, userFinal);
|
||||
expect(queries).to.have.lengthOf(4);
|
||||
|
||||
const tempTableName = getTempTableName(queries[0]);
|
||||
|
@ -265,7 +248,7 @@ describe('field queries', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const queries = await userChangeQueries(userInitial, userFinal);
|
||||
const { queries } = await userChangeQueries(userInitial, userFinal);
|
||||
expect(queries).to.have.lengthOf(4);
|
||||
|
||||
const tempTableName = getTempTableName(queries[0]);
|
||||
|
@ -294,10 +277,7 @@ describe('field queries', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const queries = await userChangeQueries(userInitial, userFinal, {
|
||||
...defaultPromptResponse,
|
||||
allowDataLoss: true,
|
||||
});
|
||||
const { queries } = await userChangeQueries(userInitial, userFinal);
|
||||
|
||||
expect(queries).to.have.lengthOf(4);
|
||||
|
||||
|
@ -320,10 +300,7 @@ describe('field queries', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const queries = await userChangeQueries(userInitial, userFinal, {
|
||||
...defaultPromptResponse,
|
||||
allowDataLoss: true,
|
||||
});
|
||||
const { queries } = await userChangeQueries(userInitial, userFinal);
|
||||
expect(queries).to.have.lengthOf(4);
|
||||
|
||||
const tempTableName = getTempTableName(queries[0]);
|
||||
|
@ -347,13 +324,13 @@ describe('field queries', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const queries = await userChangeQueries(userInitial, userFinal);
|
||||
const { queries } = await userChangeQueries(userInitial, userFinal);
|
||||
expect(queries).to.deep.equal(['ALTER TABLE "Users" ADD COLUMN "birthday" text']);
|
||||
});
|
||||
|
||||
it('when adding a required field with default', async () => {
|
||||
const defaultDate = new Date('2023-01-01');
|
||||
const userFinal = collectionSchema.parse({
|
||||
const userFinal = defineCollection({
|
||||
...userInitial,
|
||||
fields: {
|
||||
...userInitial.fields,
|
||||
|
@ -361,7 +338,7 @@ describe('field queries', () => {
|
|||
},
|
||||
});
|
||||
|
||||
const queries = await userChangeQueries(userInitial, userFinal);
|
||||
const { queries } = await userChangeQueries(userInitial, userFinal);
|
||||
expect(queries).to.deep.equal([
|
||||
`ALTER TABLE "Users" ADD COLUMN "birthday" text NOT NULL DEFAULT '${defaultDate.toISOString()}'`,
|
||||
]);
|
||||
|
@ -378,7 +355,7 @@ describe('field queries', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const queries = await userChangeQueries(userInitial, userFinal);
|
||||
const { queries } = await userChangeQueries(userInitial, userFinal);
|
||||
expect(queries).to.deep.equal([
|
||||
'ALTER TABLE "Users" DROP COLUMN "age"',
|
||||
'ALTER TABLE "Users" DROP COLUMN "mi"',
|
||||
|
|
Loading…
Add table
Reference in a new issue