@studiocms/migrator 0.0.0-beta.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +63 -0
  3. package/package.json +55 -7
  4. package/public/favicon.svg +5 -0
  5. package/src/components/ErrorCard.astro +20 -0
  6. package/src/components/MigrationMeta.astro +74 -0
  7. package/src/components/MigrationStatus.astro +71 -0
  8. package/src/components/PageHeader.astro +24 -0
  9. package/src/components/StatusTables.astro +45 -0
  10. package/src/db/astro-db-drizzle-client.ts +14 -0
  11. package/src/db/astro-db-schema.ts +193 -0
  12. package/src/db/astrodb.ts +88 -0
  13. package/src/db/client.ts +156 -0
  14. package/src/db/drizzle-schema.ts +54 -0
  15. package/src/env.d.ts +3 -0
  16. package/src/fonts/css/onest-variable.css +16 -0
  17. package/src/fonts/woff2/onest-variable.woff2 +0 -0
  18. package/src/layouts/Layout.astro +30 -0
  19. package/src/lib/astro-db-drizzle-compat/core-types.ts +88 -0
  20. package/src/lib/astro-db-drizzle-compat/error-map.ts +105 -0
  21. package/src/lib/astro-db-drizzle-compat/index.ts +149 -0
  22. package/src/lib/astro-db-drizzle-compat/schemas.ts +249 -0
  23. package/src/lib/astro-db-drizzle-compat/types.ts +141 -0
  24. package/src/lib/astro-db-drizzle-compat/utils.ts +55 -0
  25. package/src/lib/astro-db-drizzle-compat/virtual.ts +91 -0
  26. package/src/lib/errors.ts +57 -0
  27. package/src/lib/logger.ts +4 -0
  28. package/src/lib/remapUtils.ts +170 -0
  29. package/src/lib/response-utils.ts +12 -0
  30. package/src/lib/tableMap.ts +236 -0
  31. package/src/pages/data-migrations.ts +268 -0
  32. package/src/pages/index.astro +259 -0
  33. package/src/pages/schema-migrations.ts +31 -0
  34. package/src/styles/global.css +165 -0
  35. package/start.mjs +163 -0
  36. package/utils/logger.mjs +93 -0
  37. package/utils/resolver.mjs +60 -0
@@ -0,0 +1,249 @@
1
+ /** biome-ignore-all lint/suspicious/noExplicitAny: source from `@astrojs/db` */
2
+ /** biome-ignore-all lint/correctness/noUnusedVariables: source from `@astrojs/db` */
3
+ import { type ZodTypeDef, z } from 'astro/zod';
4
+ import { SQL } from 'drizzle-orm';
5
+ import { SQLiteAsyncDialect } from 'drizzle-orm/sqlite-core';
6
+ import type { NumberColumn, TextColumn } from './core-types.js';
7
+ import { errorMap } from './error-map.js';
8
+ import { SERIALIZED_SQL_KEY, type SerializedSQL } from './types.js';
9
+ import { mapObject } from './utils.js';
10
+
11
+ export type MaybeArray<T> = T | T[];
12
+
13
+ // Transform to serializable object for migration files
14
+ const sqlite = new SQLiteAsyncDialect();
15
+
16
+ const sqlSchema = z.instanceof(SQL<any>).transform(
17
+ (sqlObj): SerializedSQL => ({
18
+ [SERIALIZED_SQL_KEY]: true,
19
+ sql: sqlite.sqlToQuery(sqlObj).sql,
20
+ })
21
+ );
22
+
23
+ const baseColumnSchema = z.object({
24
+ label: z.string().optional(),
25
+ optional: z.boolean().optional().default(false),
26
+ unique: z.boolean().optional().default(false),
27
+ deprecated: z.boolean().optional().default(false),
28
+
29
+ // Defined when `defineDb()` is called to resolve `references`
30
+ name: z.string().optional(),
31
+ collection: z.string().optional(),
32
+ });
33
+
34
+ export const booleanColumnSchema = z.object({
35
+ type: z.literal('boolean'),
36
+ schema: baseColumnSchema.extend({
37
+ default: z.union([z.boolean(), sqlSchema]).optional(),
38
+ }),
39
+ });
40
+
41
+ const numberColumnBaseSchema = baseColumnSchema.omit({ optional: true }).and(
42
+ z.union([
43
+ z.object({
44
+ primaryKey: z.literal(false).optional().default(false),
45
+ optional: baseColumnSchema.shape.optional,
46
+ default: z.union([z.number(), sqlSchema]).optional(),
47
+ }),
48
+ z.object({
49
+ // `integer primary key` uses ROWID as the default value.
50
+ // `optional` and `default` do not have an effect,
51
+ // so disable these config options for primary keys.
52
+ primaryKey: z.literal(true),
53
+ optional: z.literal(false).optional(),
54
+ default: z.literal(undefined).optional(),
55
+ }),
56
+ ])
57
+ );
58
+
59
+ export const numberColumnOptsSchema: z.ZodType<
60
+ z.infer<typeof numberColumnBaseSchema> & {
61
+ // ReferenceableColumn creates a circular type. Define ZodType to resolve.
62
+ references?: NumberColumn;
63
+ },
64
+ ZodTypeDef,
65
+ z.input<typeof numberColumnBaseSchema> & {
66
+ references?: () => z.input<typeof numberColumnSchema>;
67
+ }
68
+ > = numberColumnBaseSchema.and(
69
+ z.object({
70
+ references: z
71
+ .function()
72
+ .returns(z.lazy(() => numberColumnSchema))
73
+ .optional()
74
+ .transform((fn) => fn?.()),
75
+ })
76
+ );
77
+
78
+ export const numberColumnSchema = z.object({
79
+ type: z.literal('number'),
80
+ schema: numberColumnOptsSchema,
81
+ });
82
+
83
+ const textColumnBaseSchema = baseColumnSchema
84
+ .omit({ optional: true })
85
+ .extend({
86
+ default: z.union([z.string(), sqlSchema]).optional(),
87
+ multiline: z.boolean().optional(),
88
+ enum: z.tuple([z.string()]).rest(z.string()).optional(), // At least one value required,
89
+ })
90
+ .and(
91
+ z.union([
92
+ z.object({
93
+ primaryKey: z.literal(false).optional().default(false),
94
+ optional: baseColumnSchema.shape.optional,
95
+ }),
96
+ z.object({
97
+ // text primary key allows NULL values.
98
+ // NULL values bypass unique checks, which could
99
+ // lead to duplicate URLs per record.
100
+ // disable `optional` for primary keys.
101
+ primaryKey: z.literal(true),
102
+ optional: z.literal(false).optional(),
103
+ }),
104
+ ])
105
+ );
106
+
107
+ export const textColumnOptsSchema: z.ZodType<
108
+ z.infer<typeof textColumnBaseSchema> & {
109
+ // ReferenceableColumn creates a circular type. Define ZodType to resolve.
110
+ references?: TextColumn;
111
+ },
112
+ ZodTypeDef,
113
+ z.input<typeof textColumnBaseSchema> & {
114
+ references?: () => z.input<typeof textColumnSchema>;
115
+ }
116
+ > = textColumnBaseSchema.and(
117
+ z.object({
118
+ references: z
119
+ .function()
120
+ .returns(z.lazy(() => textColumnSchema))
121
+ .optional()
122
+ .transform((fn) => fn?.()),
123
+ })
124
+ );
125
+
126
+ export const textColumnSchema = z.object({
127
+ type: z.literal('text'),
128
+ schema: textColumnOptsSchema,
129
+ });
130
+
131
+ export const dateColumnSchema = z.object({
132
+ type: z.literal('date'),
133
+ schema: baseColumnSchema.extend({
134
+ default: z
135
+ .union([
136
+ sqlSchema,
137
+ // transform to ISO string for serialization
138
+ z
139
+ .date()
140
+ .transform((d) => d.toISOString()),
141
+ ])
142
+ .optional(),
143
+ }),
144
+ });
145
+
146
+ export const jsonColumnSchema = z.object({
147
+ type: z.literal('json'),
148
+ schema: baseColumnSchema.extend({
149
+ default: z.unknown().optional(),
150
+ }),
151
+ });
152
+
153
+ export const columnSchema = z.discriminatedUnion('type', [
154
+ booleanColumnSchema,
155
+ numberColumnSchema,
156
+ textColumnSchema,
157
+ dateColumnSchema,
158
+ jsonColumnSchema,
159
+ ]);
160
+ export const referenceableColumnSchema = z.union([textColumnSchema, numberColumnSchema]);
161
+
162
+ export const columnsSchema = z.record(columnSchema);
163
+
164
+ type ForeignKeysInput = {
165
+ columns: MaybeArray<string>;
166
+ references: () => MaybeArray<Omit<z.input<typeof referenceableColumnSchema>, 'references'>>;
167
+ };
168
+
169
+ type ForeignKeysOutput = Omit<ForeignKeysInput, 'references'> & {
170
+ // reference fn called in `transform`. Ensures output is JSON serializable.
171
+ references: MaybeArray<Omit<z.output<typeof referenceableColumnSchema>, 'references'>>;
172
+ };
173
+
174
+ const foreignKeysSchema: z.ZodType<ForeignKeysOutput, ZodTypeDef, ForeignKeysInput> = z.object({
175
+ columns: z.string().or(z.array(z.string())),
176
+ references: z
177
+ .function()
178
+ .returns(z.lazy(() => referenceableColumnSchema.or(z.array(referenceableColumnSchema))))
179
+ .transform((fn) => fn()),
180
+ });
181
+
182
+ export const resolvedIndexSchema = z.object({
183
+ on: z.string().or(z.array(z.string())),
184
+ unique: z.boolean().optional(),
185
+ });
186
+ /** @deprecated */
187
+ const legacyIndexesSchema = z.record(resolvedIndexSchema);
188
+
189
+ export const indexSchema = z.object({
190
+ on: z.string().or(z.array(z.string())),
191
+ unique: z.boolean().optional(),
192
+ name: z.string().optional(),
193
+ });
194
+ const indexesSchema = z.array(indexSchema);
195
+
196
+ export const tableSchema = z.object({
197
+ columns: columnsSchema,
198
+ indexes: indexesSchema.or(legacyIndexesSchema).optional(),
199
+ foreignKeys: z.array(foreignKeysSchema).optional(),
200
+ deprecated: z.boolean().optional().default(false),
201
+ });
202
+
203
+ export const tablesSchema = z.preprocess((rawTables) => {
204
+ // Use `z.any()` to avoid breaking object references
205
+ const tables = z.record(z.any()).parse(rawTables, { errorMap });
206
+ for (const [tableName, table] of Object.entries(tables)) {
207
+ // Append table and column names to columns.
208
+ // Used to track table info for references.
209
+ table.getName = () => tableName;
210
+ const { columns } = z.object({ columns: z.record(z.any()) }).parse(table, { errorMap });
211
+ for (const [columnName, column] of Object.entries(columns)) {
212
+ column.schema.name = columnName;
213
+ column.schema.collection = tableName;
214
+ }
215
+ }
216
+ return rawTables;
217
+ }, z.record(tableSchema));
218
+
219
+ export const dbConfigSchema = z
220
+ .object({
221
+ tables: tablesSchema.optional(),
222
+ })
223
+ .transform(({ tables = {}, ...config }) => {
224
+ return {
225
+ ...config,
226
+ tables: mapObject(tables, (tableName, table) => {
227
+ const { indexes = {} } = table;
228
+ if (!Array.isArray(indexes)) {
229
+ return { ...table, indexes };
230
+ }
231
+ const resolvedIndexes: Record<string, z.infer<typeof resolvedIndexSchema>> = {};
232
+ for (const index of indexes) {
233
+ if (index.name) {
234
+ const { name, ...rest } = index;
235
+ resolvedIndexes[index.name] = rest;
236
+ continue;
237
+ }
238
+ // Sort index columns to ensure consistent index names
239
+ const indexOn = Array.isArray(index.on) ? index.on.sort().join('_') : index.on;
240
+ const name = `${tableName}_${indexOn}_idx`;
241
+ resolvedIndexes[name] = index;
242
+ }
243
+ return {
244
+ ...table,
245
+ indexes: resolvedIndexes,
246
+ };
247
+ }),
248
+ };
249
+ });
@@ -0,0 +1,141 @@
1
+ /** biome-ignore-all lint/suspicious/noExplicitAny: source from `@astrojs/db` */
2
+ import type { ColumnBaseConfig, ColumnDataType } from 'drizzle-orm';
3
+ import type { SQLiteColumn, SQLiteTableWithColumns } from 'drizzle-orm/sqlite-core';
4
+ import type { ColumnsConfig, DBColumn, OutputColumnsConfig } from './core-types.js';
5
+
6
+ type GeneratedConfig<T extends ColumnDataType = ColumnDataType> = Pick<
7
+ ColumnBaseConfig<T, string>,
8
+ 'name' | 'tableName' | 'notNull' | 'hasDefault' | 'hasRuntimeDefault' | 'isPrimaryKey'
9
+ >;
10
+
11
+ type AstroText<
12
+ T extends GeneratedConfig<'string'>,
13
+ E extends readonly [string, ...string[]] | string,
14
+ > = SQLiteColumn<
15
+ T & {
16
+ data: E extends readonly (infer U)[] ? U : string;
17
+ dataType: 'string';
18
+ columnType: 'SQLiteText';
19
+ driverParam: string;
20
+ enumValues: E extends [string, ...string[]] ? E : never;
21
+ baseColumn: never;
22
+ isAutoincrement: boolean;
23
+ identity: undefined;
24
+ generated: undefined;
25
+ }
26
+ >;
27
+
28
+ type AstroDate<T extends GeneratedConfig<'custom'>> = SQLiteColumn<
29
+ T & {
30
+ data: Date;
31
+ dataType: 'custom';
32
+ columnType: 'SQLiteCustomColumn';
33
+ driverParam: string;
34
+ enumValues: never;
35
+ baseColumn: never;
36
+ isAutoincrement: boolean;
37
+ identity: undefined;
38
+ generated: undefined;
39
+ }
40
+ >;
41
+
42
+ type AstroBoolean<T extends GeneratedConfig<'boolean'>> = SQLiteColumn<
43
+ T & {
44
+ data: boolean;
45
+ dataType: 'boolean';
46
+ columnType: 'SQLiteBoolean';
47
+ driverParam: number;
48
+ enumValues: never;
49
+ baseColumn: never;
50
+ isAutoincrement: boolean;
51
+ identity: undefined;
52
+ generated: undefined;
53
+ }
54
+ >;
55
+
56
+ type AstroNumber<T extends GeneratedConfig<'number'>> = SQLiteColumn<
57
+ T & {
58
+ data: number;
59
+ dataType: 'number';
60
+ columnType: 'SQLiteInteger';
61
+ driverParam: number;
62
+ enumValues: never;
63
+ baseColumn: never;
64
+ isAutoincrement: boolean;
65
+ identity: undefined;
66
+ generated: undefined;
67
+ }
68
+ >;
69
+
70
+ type AstroJson<T extends GeneratedConfig<'custom'>> = SQLiteColumn<
71
+ T & {
72
+ data: unknown;
73
+ dataType: 'custom';
74
+ columnType: 'SQLiteCustomColumn';
75
+ driverParam: string;
76
+ enumValues: never;
77
+ baseColumn: never;
78
+ isAutoincrement: boolean;
79
+ identity: undefined;
80
+ generated: undefined;
81
+ }
82
+ >;
83
+
84
+ type Column<
85
+ T extends DBColumn['type'],
86
+ E extends readonly [string, ...string[]] | string,
87
+ S extends GeneratedConfig,
88
+ > = T extends 'boolean'
89
+ ? AstroBoolean<S>
90
+ : T extends 'number'
91
+ ? AstroNumber<S>
92
+ : T extends 'text'
93
+ ? AstroText<S, E>
94
+ : T extends 'date'
95
+ ? AstroDate<S>
96
+ : T extends 'json'
97
+ ? AstroJson<S>
98
+ : never;
99
+
100
+ export type Table<
101
+ TTableName extends string,
102
+ TColumns extends OutputColumnsConfig | ColumnsConfig,
103
+ > = SQLiteTableWithColumns<{
104
+ name: TTableName;
105
+ schema: undefined;
106
+ dialect: 'sqlite';
107
+ columns: {
108
+ [K in Extract<keyof TColumns, string>]: Column<
109
+ TColumns[K]['type'],
110
+ TColumns[K]['schema'] extends { enum: infer E }
111
+ ? E extends readonly [string, ...string[]]
112
+ ? E
113
+ : string
114
+ : string,
115
+ {
116
+ tableName: TTableName;
117
+ name: K;
118
+ isPrimaryKey: TColumns[K]['schema'] extends { primaryKey: true } ? true : false;
119
+ hasDefault: TColumns[K]['schema'] extends { default: NonNullable<unknown> }
120
+ ? true
121
+ : TColumns[K]['schema'] extends { primaryKey: true }
122
+ ? true
123
+ : false;
124
+ hasRuntimeDefault: TColumns[K]['schema'] extends { default: NonNullable<unknown> }
125
+ ? true
126
+ : false;
127
+ notNull: TColumns[K]['schema']['optional'] extends true ? false : true;
128
+ }
129
+ >;
130
+ };
131
+ }>;
132
+
133
+ export const SERIALIZED_SQL_KEY = '__serializedSQL';
134
+ export type SerializedSQL = {
135
+ [SERIALIZED_SQL_KEY]: true;
136
+ sql: string;
137
+ };
138
+
139
+ export function isSerializedSQL(value: any): value is SerializedSQL {
140
+ return typeof value === 'object' && value !== null && SERIALIZED_SQL_KEY in value;
141
+ }
@@ -0,0 +1,55 @@
1
+ /** biome-ignore-all lint/suspicious/noExplicitAny: source from `@astrojs/db` */
2
+ import { LibsqlError } from '@libsql/client';
3
+ import { AstroError } from 'astro/errors';
4
+ import type { DBColumn } from './core-types.js';
5
+
6
+ export function hasPrimaryKey(column: DBColumn) {
7
+ return 'primaryKey' in column.schema && !!column.schema.primaryKey;
8
+ }
9
+
10
+ const isWindows = process?.platform === 'win32';
11
+
12
+ export class AstroDbError extends AstroError {
13
+ name = 'Astro DB Error';
14
+ }
15
+
16
+ export function isDbError(err: unknown): err is LibsqlError {
17
+ return err instanceof LibsqlError || (err instanceof Error && (err as any).libsqlError === true);
18
+ }
19
+
20
+ function slash(path: string) {
21
+ const isExtendedLengthPath = path.startsWith('\\\\?\\');
22
+
23
+ if (isExtendedLengthPath) {
24
+ return path;
25
+ }
26
+
27
+ return path.replace(/\\/g, '/');
28
+ }
29
+
30
+ export function pathToFileURL(path: string): URL {
31
+ if (isWindows) {
32
+ let slashed = slash(path);
33
+ // Windows like C:/foo/bar
34
+ if (!slashed.startsWith('/')) {
35
+ slashed = `/${slashed}`;
36
+ }
37
+ return new URL(`file://${slashed}`);
38
+ }
39
+
40
+ // Unix is easy
41
+ return new URL(`file://${path}`);
42
+ }
43
+
44
+ /**
45
+ * Map an object's values to a new set of values
46
+ * while preserving types.
47
+ */
48
+ export function mapObject<T, U = T>(
49
+ item: Record<string, T>,
50
+ callback: (key: string, value: T) => U
51
+ ): Record<string, U> {
52
+ return Object.fromEntries(
53
+ Object.entries(item).map(([key, value]) => [key, callback(key, value)])
54
+ );
55
+ }
@@ -0,0 +1,91 @@
1
+ import { sql as _sql } from 'drizzle-orm';
2
+ import type {
3
+ BooleanColumnInput,
4
+ ColumnsConfig,
5
+ DateColumnInput,
6
+ JsonColumnInput,
7
+ NumberColumnOpts,
8
+ TableConfig,
9
+ TextColumnOpts,
10
+ } from './core-types.js';
11
+
12
+ function createColumn<S extends string, T extends Record<string, unknown>>(type: S, schema: T) {
13
+ return {
14
+ type,
15
+ /**
16
+ * @internal
17
+ */
18
+ schema,
19
+ };
20
+ }
21
+
22
+ export const column = {
23
+ number: <T extends NumberColumnOpts>(opts: T = {} as T) => {
24
+ return createColumn('number', opts) satisfies { type: 'number' };
25
+ },
26
+ boolean: <T extends BooleanColumnInput['schema']>(opts: T = {} as T) => {
27
+ return createColumn('boolean', opts) satisfies { type: 'boolean' };
28
+ },
29
+ text: <
30
+ T extends TextColumnOpts,
31
+ const E extends T['enum'] extends readonly [string, ...string[]]
32
+ ? Omit<T, 'enum'> & T['enum']
33
+ : T,
34
+ >(
35
+ opts: E = {} as E
36
+ ) => {
37
+ return createColumn('text', opts) satisfies { type: 'text' };
38
+ },
39
+ date<T extends DateColumnInput['schema']>(opts: T = {} as T) {
40
+ return createColumn('date', opts) satisfies { type: 'date' };
41
+ },
42
+ json<T extends JsonColumnInput['schema']>(opts: T = {} as T) {
43
+ return createColumn('json', opts) satisfies { type: 'json' };
44
+ },
45
+ };
46
+
47
+ export function defineTable<TColumns extends ColumnsConfig>(userConfig: TableConfig<TColumns>) {
48
+ return userConfig;
49
+ }
50
+
51
+ // Exports a few common expressions
52
+ export const NOW = _sql`CURRENT_TIMESTAMP`;
53
+ export const TRUE = _sql`TRUE`;
54
+ export const FALSE = _sql`FALSE`;
55
+
56
+ export {
57
+ and,
58
+ asc,
59
+ avg,
60
+ avgDistinct,
61
+ between,
62
+ count,
63
+ countDistinct,
64
+ desc,
65
+ eq,
66
+ exists,
67
+ gt,
68
+ gte,
69
+ ilike,
70
+ inArray,
71
+ isNotNull,
72
+ isNull,
73
+ like,
74
+ lt,
75
+ lte,
76
+ max,
77
+ min,
78
+ ne,
79
+ not,
80
+ notBetween,
81
+ notExists,
82
+ notIlike,
83
+ notInArray,
84
+ or,
85
+ sql,
86
+ sum,
87
+ sumDistinct,
88
+ } from 'drizzle-orm';
89
+
90
+ export { alias } from 'drizzle-orm/sqlite-core';
91
+ export { isDbError } from './utils.js';
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Error case for missing required AstroDB tables in the connected database.
3
+ */
4
+ export const AstroDbTableError = {
5
+ title: 'Error: Missing Required AstroDB Tables',
6
+ description:
7
+ 'It seems that not all required StudioCMS AstroDB tables were found in your connected database. Please ensure that you have connected to the correct database that contains your previous StudioCMS data.',
8
+ };
9
+
10
+ /**
11
+ * Error case for pending schema migrations in the connected database.
12
+ */
13
+ export const PendingSchemaMigrationsError = {
14
+ title: 'Error: Pending Schema Migrations Detected',
15
+ description:
16
+ 'Your database has pending schema migrations that need to be applied before proceeding with the migration. Please run the necessary migrations to update your database schema to the latest version before attempting data migration.',
17
+ };
18
+
19
+ /**
20
+ * Error case for data migration not being available due to existing data.
21
+ */
22
+ export const DataMigrationNotAvailableError = {
23
+ title: 'Error: Data Migration Not Available',
24
+ description:
25
+ 'Data migration cannot proceed because there is existing data in the target database. Please ensure that the target database is empty before attempting to migrate data.',
26
+ };
27
+
28
+ /**
29
+ * Get an array of error cases based on the provided data.
30
+ *
31
+ * @param data - An object containing various status indicators.
32
+ * @returns An array of error case objects.
33
+ */
34
+ export const getErrorCases = (data: {
35
+ astroDBTables: string[];
36
+ astroDbTableKeys: string[];
37
+ appliedMigrations: number;
38
+ migrationTotal: number;
39
+ dataMigrationStatus: {
40
+ canMigrate: boolean;
41
+ };
42
+ }) => {
43
+ return [
44
+ {
45
+ condition: data.astroDBTables.length < data.astroDbTableKeys.length,
46
+ ...AstroDbTableError,
47
+ },
48
+ {
49
+ condition: data.appliedMigrations < data.migrationTotal,
50
+ ...PendingSchemaMigrationsError,
51
+ },
52
+ {
53
+ condition: data.dataMigrationStatus.canMigrate === false,
54
+ ...DataMigrationNotAvailableError,
55
+ },
56
+ ];
57
+ };
@@ -0,0 +1,4 @@
1
+ import { Logger } from '../../utils/logger.mjs';
2
+
3
+ export const logger = new Logger({ level: 'info' }, '@studiocms/migrator');
4
+ export default logger;