0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-01-20 22:12:38 -05:00

feat: MVP to use libsql in dev

This commit is contained in:
bholmesdev 2024-01-08 18:02:24 -05:00 committed by Nate Moore
parent c6477107a2
commit 395a8a1400
11 changed files with 591 additions and 191 deletions

View file

@ -7,7 +7,7 @@ import {
type TextField,
type collectionSchema,
collectionsSchema,
} from 'somewhere';
} from './types.js';
import { z } from 'zod';
export const adjustedConfigSchema = z.object({
@ -21,7 +21,7 @@ export const astroConfigWithDBValidator = z.object({
});
export function defineCollection(
userConfig: z.input<typeof collectionSchema>,
userConfig: z.input<typeof collectionSchema>
): z.input<typeof collectionSchema> {
return userConfig;
}

14
packages/db/src/consts.ts Normal file
View file

@ -0,0 +1,14 @@
import { readFileSync } from 'node:fs';
export const PACKAGE_NAME = JSON.parse(
readFileSync(new URL('../package.json', import.meta.url), 'utf8')
).name;
export const INTERNAL_MOD_IMPORT = JSON.stringify(`${PACKAGE_NAME}/internal`);
export const DRIZZLE_MOD_IMPORT = JSON.stringify(`${PACKAGE_NAME}/internal-drizzle`);
export const SUPPORTED_SEED_FILES = ['db.seed.js', 'db.seed.mjs', 'db.seed.mts', 'db.seed.ts'];
export const DB_TYPES_FILE = 'db-types.d.ts';
export const VIRTUAL_MODULE_ID = 'astro:db';

View file

@ -1 +1,3 @@
export { defineCollection, field } from './config.js';
export { integration as default } from './integration.js';

View file

@ -0,0 +1,33 @@
import type { AstroIntegration } from 'astro';
import { vitePluginDb } from './vite-plugin-db.js';
import { vitePluginInjectEnvTs } from './vite-plugin-inject-env-ts.js';
import { typegen } from './typegen.js';
import { collectionsSchema } from './types.js';
export function integration(): AstroIntegration {
return {
name: 'astro:db',
hooks: {
async 'astro:config:setup'({ updateConfig, config }) {
// TODO: refine where we load collections
// @matthewp: may want to load collections by path at runtime
const collections = collectionsSchema.parse(config.db?.collections ?? {});
updateConfig({
vite: {
plugins: [
// TODO: figure out when vite.Plugin doesn't line up with these types
// @ts-ignore
vitePluginDb({
collections,
root: config.root,
}),
// @ts-ignore
vitePluginInjectEnvTs(config),
],
},
});
await typegen({ collections, root: config.root });
},
},
};
}

View file

@ -0,0 +1,25 @@
// Drizzle utilities we expose directly from `astro:db`
export {
sql,
eq,
gt,
gte,
lt,
lte,
ne,
isNull,
isNotNull,
inArray,
notInArray,
exists,
notExists,
between,
notBetween,
like,
notIlike,
not,
asc,
desc,
and,
or,
} from 'drizzle-orm';

View file

@ -1,25 +0,0 @@
import { createClient } from '@libsql/client';
import type { DBCollections } from 'circle-rhyme-yes-measure';
import { type SQL, sql } from 'drizzle-orm';
import { LibSQLDatabase, drizzle } from 'drizzle-orm/libsql';
import { getCreateTableQuery } from './cli/sync/queries.js';
export async function createLocalDb(collections: DBCollections) {
const client = createClient({ url: ':memory:' });
const db = drizzle(client);
await createDbTables(db, collections);
return db;
}
async function createDbTables(db: LibSQLDatabase, collections: DBCollections) {
const setupQueries: SQL[] = [];
for (const [name, collection] of Object.entries(collections)) {
const dropQuery = sql.raw(`DROP TABLE IF EXISTS ${name}`);
const createQuery = sql.raw(getCreateTableQuery(name, collection));
setupQueries.push(dropQuery, createQuery);
}
for (const q of setupQueries) {
await db.run(q);
}
}

View file

@ -1,112 +1,262 @@
import type { ColumnBaseConfig, ColumnDataType } from 'drizzle-orm';
import type { SQLiteColumn, SQLiteTableWithColumns, TableConfig } from 'drizzle-orm/sqlite-core';
import type { SqliteRemoteDatabase } from 'drizzle-orm/sqlite-proxy';
import { createClient } from '@libsql/client';
import type {
BooleanField,
DBCollection,
DBCollections,
DBField,
DateField,
FieldType,
JsonField,
NumberField,
TextField,
} from './types.js';
import { type LibSQLDatabase, drizzle } from 'drizzle-orm/libsql';
import { SQLiteAsyncDialect } from 'drizzle-orm/sqlite-core';
import { bold } from 'kleur/colors';
import { type SQL, type ColumnBuilderBaseConfig, type ColumnDataType, sql } from 'drizzle-orm';
import {
customType,
integer,
sqliteTable,
text,
type SQLiteColumnBuilderBase,
} from 'drizzle-orm/sqlite-core';
import { z } from 'zod';
import { nanoid } from 'nanoid';
export { collectionToTable, createDb } from 'circle-rhyme-yes-measure';
export {
sql,
eq,
gt,
gte,
lt,
lte,
ne,
isNull,
isNotNull,
inArray,
notInArray,
exists,
notExists,
between,
notBetween,
like,
notIlike,
not,
asc,
desc,
and,
or,
} from 'drizzle-orm';
export type SqliteDB = SqliteRemoteDatabase;
export type {
AstroTable,
AstroText,
AstroDate,
AstroBoolean,
AstroNumber,
AstroJson,
AstroId,
} from './types.js';
export type AstroTable<T extends Pick<TableConfig, 'name' | 'columns'>> = SQLiteTableWithColumns<
T & {
schema: undefined;
dialect: 'sqlite';
const sqlite = new SQLiteAsyncDialect();
export async function createDb(collections: DBCollections) {
const client = createClient({ url: ':memory:' });
const db = drizzle(client);
await createDbTables(db, collections);
return db;
}
async function createDbTables(db: LibSQLDatabase, collections: DBCollections) {
const setupQueries: SQL[] = [];
for (const [name, collection] of Object.entries(collections)) {
const dropQuery = sql.raw(`DROP TABLE IF EXISTS ${name}`);
const createQuery = sql.raw(getCreateTableQuery(name, collection));
setupQueries.push(dropQuery, createQuery);
}
>;
type GeneratedConfig<T extends ColumnDataType> = Pick<
ColumnBaseConfig<T, string>,
'name' | 'tableName' | 'notNull' | 'hasDefault'
>;
export type AstroText<T extends GeneratedConfig<'string'>> = SQLiteColumn<
T & {
data: string;
dataType: 'string';
columnType: 'SQLiteText';
driverParam: string;
enumValues: never;
baseColumn: never;
for (const q of setupQueries) {
await db.run(q);
}
}
export function getCreateTableQuery(collectionName: string, collection: DBCollection) {
let query = `CREATE TABLE ${sqlite.escapeName(collectionName)} (`;
const colQueries = ['"id" text PRIMARY KEY'];
for (const [columnName, column] of Object.entries(collection.fields)) {
const colQuery = `${sqlite.escapeName(columnName)} ${schemaTypeToSqlType(
column.type
)}${getModifiers(columnName, column)}`;
colQueries.push(colQuery);
}
query += colQueries.join(', ') + ')';
return query;
}
function schemaTypeToSqlType(type: FieldType): 'text' | 'integer' {
switch (type) {
case 'date':
case 'text':
case 'json':
return 'text';
case 'number':
case 'boolean':
return 'integer';
}
}
function getModifiers(columnName: string, column: DBField) {
let modifiers = '';
if (!column.optional) {
modifiers += ' NOT NULL';
}
if (column.unique) {
modifiers += ' UNIQUE';
}
if (hasDefault(column)) {
modifiers += ` DEFAULT ${getDefaultValueSql(columnName, column)}`;
}
return modifiers;
}
// Using `DBField` will not narrow `default` based on the column `type`
// Handle each field separately
type WithDefaultDefined<T extends DBField> = T & Required<Pick<T, 'default'>>;
type DBFieldWithDefault =
| WithDefaultDefined<TextField>
| WithDefaultDefined<DateField>
| WithDefaultDefined<NumberField>
| WithDefaultDefined<BooleanField>
| WithDefaultDefined<JsonField>;
// Type narrowing the default fails on union types, so use a type guard
function hasDefault(field: DBField): field is DBFieldWithDefault {
return field.default !== undefined;
}
function hasRuntimeDefault(field: DBField): field is DBFieldWithDefault {
return field.type === 'date' && field.default === 'now';
}
function getDefaultValueSql(columnName: string, column: DBFieldWithDefault): string {
switch (column.type) {
case 'boolean':
return column.default ? 'TRUE' : 'FALSE';
case 'number':
return `${column.default}`;
case 'text':
return sqlite.escapeString(column.default);
case 'date':
return column.default === 'now' ? 'CURRENT_TIMESTAMP' : sqlite.escapeString(column.default);
case 'json': {
let stringified = '';
try {
stringified = JSON.stringify(column.default);
} catch (e) {
console.info(
`Invalid default value for column ${bold(
columnName
)}. Defaults must be valid JSON when using the \`json()\` type.`
);
process.exit(0);
}
return sqlite.escapeString(stringified);
}
}
}
function generateId() {
return nanoid(12);
}
const dateType = customType<{ data: Date; driverData: string }>({
dataType() {
return 'text';
},
toDriver(value) {
return value.toISOString();
},
fromDriver(value) {
return new Date(value);
},
});
const jsonType = customType<{ data: unknown; driverData: string }>({
dataType() {
return 'text';
},
toDriver(value) {
return JSON.stringify(value);
},
fromDriver(value) {
return JSON.parse(value);
},
});
const initialColumns = {
id: text('id')
.primaryKey()
.$default(() => generateId()),
};
type D1ColumnBuilder = SQLiteColumnBuilderBase<
ColumnBuilderBaseConfig<ColumnDataType, string> & { data: unknown }
>;
export type AstroDate<T extends GeneratedConfig<'custom'>> = SQLiteColumn<
T & {
data: Date;
dataType: 'custom';
columnType: 'SQLiteCustomColumn';
driverParam: string;
enumValues: never;
baseColumn: never;
}
>;
export function collectionToTable(
name: string,
collection: DBCollection,
isJsonSerializable = true
) {
const columns: Record<string, D1ColumnBuilder> & typeof initialColumns = {
// Spread to avoid mutating `initialColumns`
...initialColumns,
};
export type AstroBoolean<T extends GeneratedConfig<'boolean'>> = SQLiteColumn<
T & {
data: boolean;
dataType: 'boolean';
columnType: 'SQLiteBoolean';
driverParam: number;
enumValues: never;
baseColumn: never;
for (const [fieldName, field] of Object.entries(collection.fields)) {
columns[fieldName] = columnMapper(fieldName, field, isJsonSerializable);
}
>;
export type AstroNumber<T extends GeneratedConfig<'number'>> = SQLiteColumn<
T & {
data: number;
dataType: 'number';
columnType: 'SQLiteInteger';
driverParam: number;
enumValues: never;
baseColumn: never;
}
>;
const table = sqliteTable(name, columns);
return table;
}
export type AstroJson<T extends GeneratedConfig<'custom'>> = SQLiteColumn<
T & {
data: unknown;
dataType: 'custom';
columnType: 'SQLiteCustomColumn';
driverParam: string;
enumValues: never;
baseColumn: never;
}
>;
function columnMapper(fieldName: string, field: DBField, isJsonSerializable: boolean) {
let c: ReturnType<
| typeof text
| typeof integer
| typeof jsonType
| typeof dateType
| typeof integer<string, 'boolean'>
>;
export type AstroId<T extends Pick<GeneratedConfig<'string'>, 'tableName'>> = SQLiteColumn<
T & {
name: 'id';
hasDefault: true;
notNull: true;
data: string;
dataType: 'custom';
columnType: 'SQLiteCustomColumn';
driverParam: string;
enumValues: never;
baseColumn: never;
switch (field.type) {
case 'text': {
c = text(fieldName);
// Duplicate default logic across cases to preserve type inference.
// No clean generic for every column builder.
if (field.default !== undefined) c = c.default(field.default);
break;
}
case 'number': {
c = integer(fieldName);
if (field.default !== undefined) c = c.default(field.default);
break;
}
case 'boolean': {
c = integer(fieldName, { mode: 'boolean' });
if (field.default !== undefined) c = c.default(field.default);
break;
}
case 'json':
c = jsonType(fieldName);
if (field.default !== undefined) c = c.default(field.default);
break;
case 'date': {
// Parse dates as strings when in JSON serializable mode
if (isJsonSerializable) {
c = text(fieldName);
if (field.default !== undefined) {
c = c.default(field.default === 'now' ? sql`CURRENT_TIMESTAMP` : field.default);
}
} else {
c = dateType(fieldName);
if (field.default !== undefined) {
c = c.default(
field.default === 'now'
? sql`CURRENT_TIMESTAMP`
: // default comes pre-transformed to an ISO string for D1 storage.
// parse back to a Date for Drizzle.
z.coerce.date().parse(field.default)
);
}
}
break;
}
}
>;
if (!field.optional) c = c.notNull();
if (field.unique) c = c.unique();
return c;
}

View file

@ -0,0 +1,64 @@
import { existsSync } from 'node:fs';
import { mkdir, writeFile } from 'node:fs/promises';
import type { DBCollection, DBCollections, FieldType } from './types.js';
import { DB_TYPES_FILE, DRIZZLE_MOD_IMPORT, INTERNAL_MOD_IMPORT } from './consts.js';
export async function typegen({ collections, root }: { collections: DBCollections; root: URL }) {
const content = `// This file is generated by \`studio sync\`
declare module 'astro:db' {
export const db: import(${INTERNAL_MOD_IMPORT}).SqliteDB;
export * from ${DRIZZLE_MOD_IMPORT}
${Object.entries(collections)
.map(([name, collection]) => generateTableType(name, collection))
.join('\n')}
}
`;
const dotAstroDir = new URL('.astro/', root);
if (!existsSync(dotAstroDir)) {
await mkdir(dotAstroDir);
}
await writeFile(new URL(DB_TYPES_FILE, dotAstroDir), content);
}
function generateTableType(name: string, collection: DBCollection): string {
let tableType = ` export const ${name}: import(${INTERNAL_MOD_IMPORT}).AstroTable<{
name: ${JSON.stringify(name)};
columns: {
id: import(${INTERNAL_MOD_IMPORT}).AstroId<{
tableName: ${JSON.stringify(name)};
}>;`;
for (const [fieldName, field] of Object.entries(collection.fields)) {
const drizzleInterface = schemaTypeToDrizzleInterface(field.type);
tableType += `
${fieldName}: import(${INTERNAL_MOD_IMPORT}).${drizzleInterface}<{
tableName: ${JSON.stringify(name)};
name: ${JSON.stringify(fieldName)};
notNull: ${field.optional ? 'false' : 'true'};
hasDefault: ${typeof field.default !== 'undefined' ? 'true' : 'false'};
}>;`;
}
tableType += `
};
}>;`;
return tableType;
}
function schemaTypeToDrizzleInterface(type: FieldType) {
switch (type) {
case 'text':
return 'AstroText';
case 'number':
return 'AstroNumber';
case 'boolean':
return 'AstroBoolean';
case 'date':
return 'AstroDate';
case 'json':
return 'AstroJson';
}
}

159
packages/db/src/types.ts Normal file
View file

@ -0,0 +1,159 @@
import type { ColumnDataType, ColumnBaseConfig } from 'drizzle-orm';
import type { SQLiteColumn, SQLiteTableWithColumns, TableConfig } from 'drizzle-orm/sqlite-core';
import { z } from 'zod';
const baseFieldSchema = z.object({
label: z.string().optional(),
optional: z.boolean().optional(),
unique: z.boolean().optional(),
});
const booleanFieldSchema = baseFieldSchema.extend({
type: z.literal('boolean'),
default: z.boolean().optional(),
});
const numberFieldSchema = baseFieldSchema.extend({
type: z.literal('number'),
default: z.number().optional(),
});
const textFieldSchema = baseFieldSchema.extend({
type: z.literal('text'),
multiline: z.boolean().optional(),
default: z.string().optional(),
});
const dateFieldSchema = baseFieldSchema.extend({
type: z.literal('date'),
default: z
.union([
z.literal('now'),
// allow date-like defaults in user config,
// transform to ISO string for D1 storage
z.coerce.date().transform((d) => d.toISOString()),
])
.optional(),
});
const jsonFieldSchema = baseFieldSchema.extend({
type: z.literal('json'),
default: z.unknown().optional(),
});
const fieldSchema = z.union([
booleanFieldSchema,
numberFieldSchema,
textFieldSchema,
dateFieldSchema,
jsonFieldSchema,
]);
const fieldsSchema = z.record(fieldSchema);
export const collectionSchema = z.object({
fields: fieldsSchema,
});
export const collectionsSchema = z.record(collectionSchema);
export type BooleanField = z.infer<typeof booleanFieldSchema>;
export type NumberField = z.infer<typeof numberFieldSchema>;
export type TextField = z.infer<typeof textFieldSchema>;
export type DateField = z.infer<typeof dateFieldSchema>;
// Type `Date` is the config input, `string` is the output for D1 storage
export type DateFieldInput = z.input<typeof dateFieldSchema>;
export type JsonField = z.infer<typeof jsonFieldSchema>;
export type FieldType =
| BooleanField['type']
| NumberField['type']
| TextField['type']
| DateField['type']
| JsonField['type'];
export type DBField = z.infer<typeof fieldSchema>;
export type DBFieldInput = DateFieldInput | BooleanField | NumberField | TextField | JsonField;
export type DBFields = z.infer<typeof fieldsSchema>;
export type DBCollection = z.infer<typeof collectionSchema>;
export type DBCollections = z.infer<typeof collectionsSchema>;
export type AstroTable<T extends Pick<TableConfig, 'name' | 'columns'>> = SQLiteTableWithColumns<
T & {
schema: undefined;
dialect: 'sqlite';
}
>;
type GeneratedConfig<T extends ColumnDataType> = Pick<
ColumnBaseConfig<T, string>,
'name' | 'tableName' | 'notNull' | 'hasDefault'
>;
export type AstroText<T extends GeneratedConfig<'string'>> = SQLiteColumn<
T & {
data: string;
dataType: 'string';
columnType: 'SQLiteText';
driverParam: string;
enumValues: never;
baseColumn: never;
}
>;
export type AstroDate<T extends GeneratedConfig<'custom'>> = SQLiteColumn<
T & {
data: Date;
dataType: 'custom';
columnType: 'SQLiteCustomColumn';
driverParam: string;
enumValues: never;
baseColumn: never;
}
>;
export type AstroBoolean<T extends GeneratedConfig<'boolean'>> = SQLiteColumn<
T & {
data: boolean;
dataType: 'boolean';
columnType: 'SQLiteBoolean';
driverParam: number;
enumValues: never;
baseColumn: never;
}
>;
export type AstroNumber<T extends GeneratedConfig<'number'>> = SQLiteColumn<
T & {
data: number;
dataType: 'number';
columnType: 'SQLiteInteger';
driverParam: number;
enumValues: never;
baseColumn: never;
}
>;
export type AstroJson<T extends GeneratedConfig<'custom'>> = SQLiteColumn<
T & {
data: unknown;
dataType: 'custom';
columnType: 'SQLiteCustomColumn';
driverParam: string;
enumValues: never;
baseColumn: never;
}
>;
export type AstroId<T extends Pick<GeneratedConfig<'string'>, 'tableName'>> = SQLiteColumn<
T & {
name: 'id';
hasDefault: true;
notNull: true;
data: string;
dataType: 'custom';
columnType: 'SQLiteCustomColumn';
driverParam: string;
enumValues: never;
baseColumn: never;
}
>;

View file

@ -1,22 +1,24 @@
import { existsSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import type { DBCollections } from 'circle-rhyme-yes-measure';
import { red } from 'kleur/colors';
import {
INTERNAL_LOCAL_PKG_IMP,
INTERNAL_PKG_IMP,
ROOT,
DRIZZLE_MOD_IMPORT,
INTERNAL_MOD_IMPORT,
SUPPORTED_SEED_FILES,
VIRTUAL_MODULE_ID,
drizzleFilterExps,
} from './consts.js';
import type { VitePlugin } from './utils.js';
import type { DBCollections } from './types.js';
import type { Plugin as VitePlugin } from 'vite';
const resolvedVirtualModuleId = '\0' + VIRTUAL_MODULE_ID;
type Opts = { mode: 'dev' } | { mode: 'prod'; projectId: string; token: string };
export function vitePluginDb(collections: DBCollections, opts: Opts): VitePlugin {
export function vitePluginDb({
collections,
root,
}: {
collections: DBCollections;
root: URL;
}): VitePlugin {
return {
name: 'astro:db',
enforce: 'pre',
@ -27,34 +29,30 @@ export function vitePluginDb(collections: DBCollections, opts: Opts): VitePlugin
},
load(id) {
if (id !== resolvedVirtualModuleId) return;
if (opts.mode === 'dev') {
return getLocalVirtualModuleContents({ collections });
}
return getProdVirtualModuleContents({
collections,
projectId: opts.projectId,
appToken: opts.token,
});
return getLocalVirtualModuleContents({ collections, root });
},
};
}
const seedErrorMessage = `${red(
'⚠️ Failed to seed data.',
'⚠️ Failed to seed data.'
)} Is the seed file out-of-date with recent schema changes?`;
export function getLocalVirtualModuleContents({ collections }: { collections: DBCollections }) {
const seedFile = SUPPORTED_SEED_FILES.map((f) => fileURLToPath(new URL(f, ROOT))).find((f) =>
existsSync(f),
export function getLocalVirtualModuleContents({
collections,
root,
}: {
collections: DBCollections;
root: URL;
}) {
const seedFile = SUPPORTED_SEED_FILES.map((f) => fileURLToPath(new URL(f, root))).find((f) =>
existsSync(f)
);
return `
import { collectionToTable } from ${INTERNAL_PKG_IMP};
import { createLocalDb } from ${INTERNAL_LOCAL_PKG_IMP};
import { collectionToTable, createDb } from ${INTERNAL_MOD_IMPORT};
export const db = await createLocalDb(${JSON.stringify(collections)});
${drizzleFilterExps}
export const db = await createDb(${JSON.stringify(collections)});
export * from ${DRIZZLE_MOD_IMPORT};
${getStringifiedCollectionExports(collections)}
@ -70,32 +68,13 @@ ${
`;
}
export function getProdVirtualModuleContents({
collections,
projectId,
appToken,
}: {
collections: DBCollections;
projectId: string;
appToken: string;
}) {
return `
import { collectionToTable, createDb } from ${INTERNAL_PKG_IMP};
export const db = createDb(${JSON.stringify(projectId)}, ${JSON.stringify(appToken)});
${drizzleFilterExps}
${getStringifiedCollectionExports(collections)}
`;
}
function getStringifiedCollectionExports(collections: DBCollections) {
return Object.entries(collections)
.map(
([name, collection]) =>
`export const ${name} = collectionToTable(${JSON.stringify(name)}, ${JSON.stringify(
collection,
)}, false)`,
collection
)}, false)`
)
.join('\n');
}

View file

@ -2,40 +2,34 @@ import { existsSync } from 'node:fs';
import { readFile, writeFile } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import type { AstroConfig } from 'astro';
import { bold, cyan } from 'kleur/colors';
import { normalizePath } from 'vite';
import { DOT_ASTRO_DIR, DB_TYPES_FILE } from './consts.js';
import type { VitePlugin } from './utils.js';
import type { Plugin as VitePlugin } from 'vite';
import { DB_TYPES_FILE } from './consts.js';
export function getEnvTsPath({ srcDir }: { srcDir: URL }) {
return new URL('env.d.ts', srcDir);
}
export function vitePluginInjectEnvTs({ config }: { config: AstroConfig }): VitePlugin {
export function vitePluginInjectEnvTs({ srcDir, root }: { srcDir: URL; root: URL }): VitePlugin {
return {
name: 'db-inject-env-ts',
// Use `post` to ensure project setup is complete
// Ex. `.astro` types have been written
enforce: 'post',
async config() {
await setUpEnvTs({ config });
await setUpEnvTs({ srcDir, root });
},
};
}
export async function setUpEnvTs({ config }: { config: AstroConfig }) {
const envTsPath = getEnvTsPath(config);
export async function setUpEnvTs({ srcDir, root }: { srcDir: URL; root: URL }) {
const envTsPath = getEnvTsPath({ srcDir });
const envTsPathRelativetoRoot = normalizePath(
path.relative(fileURLToPath(config.root), fileURLToPath(envTsPath)),
path.relative(fileURLToPath(root), fileURLToPath(envTsPath))
);
if (existsSync(envTsPath)) {
let typesEnvContents = await readFile(envTsPath, 'utf-8');
const dotAstroDir = new URL('.astro/', root);
if (!existsSync(DOT_ASTRO_DIR)) return;
if (!existsSync(dotAstroDir)) return;
const dbTypeReference = getDBTypeReference(config);
const dbTypeReference = getDBTypeReference({ srcDir, dotAstroDir });
if (!typesEnvContents.includes(dbTypeReference)) {
typesEnvContents = `${dbTypeReference}\n${typesEnvContents}`;
@ -45,10 +39,15 @@ export async function setUpEnvTs({ config }: { config: AstroConfig }) {
}
}
function getDBTypeReference({ srcDir }: { srcDir: URL }) {
function getDBTypeReference({ srcDir, dotAstroDir }: { srcDir: URL; dotAstroDir: URL }) {
const dbTypesFile = new URL(DB_TYPES_FILE, dotAstroDir);
const contentTypesRelativeToSrcDir = normalizePath(
path.relative(fileURLToPath(srcDir), fileURLToPath(DB_TYPES_FILE)),
path.relative(fileURLToPath(srcDir), fileURLToPath(dbTypesFile))
);
return `/// <reference path=${JSON.stringify(contentTypesRelativeToSrcDir)} />`;
}
function getEnvTsPath({ srcDir }: { srcDir: URL }) {
return new URL('env.d.ts', srcDir);
}