mirror of
https://github.com/withastro/astro.git
synced 2025-03-10 23:01:26 -05:00
feat: use studio connection for studio builds
This commit is contained in:
parent
9119753336
commit
4a51522beb
7 changed files with 112 additions and 68 deletions
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env node
|
||||
import { red } from 'kleur/colors';
|
||||
import { astroConfigWithDBValidator } from '../config.js';
|
||||
import { astroConfigWithDbSchema } from '../config.js';
|
||||
import { errorMap } from '../error-map.js';
|
||||
import { loadAstroConfig } from '../load-astro-config.js';
|
||||
|
||||
|
@ -26,14 +26,14 @@ async function main() {
|
|||
|
||||
async function getAstroConfigOrExit(root: string = process.cwd()) {
|
||||
const astroConfig = await loadAstroConfig(root);
|
||||
const parsed = astroConfigWithDBValidator.safeParse(astroConfig, { errorMap });
|
||||
const parsed = astroConfigWithDbSchema.safeParse(astroConfig, { errorMap });
|
||||
if (parsed.success) {
|
||||
return parsed.data;
|
||||
}
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
red('⚠️ Invalid studio config. Check your astro config file\n') +
|
||||
parsed.error.issues.map((i) => i.message).join('\n'),
|
||||
parsed.error.issues.map((i) => i.message).join('\n')
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
|
|
|
@ -10,32 +10,40 @@ import {
|
|||
} from './types.js';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const adjustedConfigSchema = z.object({
|
||||
export const dbConfigSchema = z.object({
|
||||
studio: z.boolean().optional(),
|
||||
collections: collectionsSchema.optional(),
|
||||
});
|
||||
|
||||
export type DBUserConfig = z.input<typeof adjustedConfigSchema>;
|
||||
export type DBUserConfig = z.input<typeof dbConfigSchema>;
|
||||
|
||||
export const astroConfigWithDBValidator = z.object({
|
||||
db: adjustedConfigSchema.optional(),
|
||||
export const astroConfigWithDbSchema = z.object({
|
||||
db: dbConfigSchema.optional(),
|
||||
});
|
||||
|
||||
type CollectionConfig<TFields extends z.input<typeof collectionSchema>['fields'], Writable extends boolean> =
|
||||
Writable extends true ? (
|
||||
{
|
||||
type CollectionConfig<
|
||||
TFields extends z.input<typeof collectionSchema>['fields'],
|
||||
Writable extends boolean,
|
||||
> = Writable extends true
|
||||
? {
|
||||
fields: TFields;
|
||||
// TODO: type inference based on field type. Just `any` for now.
|
||||
seed?: Writable extends false ? never : () => Array<Record<keyof TFields, any> & { id?: string }>;
|
||||
seed?: Writable extends false
|
||||
? never
|
||||
: () => Array<Record<keyof TFields, any> & { id?: string }>;
|
||||
}
|
||||
) : (
|
||||
{
|
||||
: {
|
||||
fields: TFields;
|
||||
// TODO: type inference based on field type. Just `any` for now.
|
||||
data?: Writable extends true ? never : () => Array<Record<keyof TFields, any> & { id?: string }>;
|
||||
}
|
||||
)
|
||||
data?: Writable extends true
|
||||
? never
|
||||
: () => Array<Record<keyof TFields, any> & { id?: string }>;
|
||||
};
|
||||
|
||||
type ResolvedCollectionConfig<TFields extends z.input<typeof collectionSchema>['fields'], Writable extends boolean> = CollectionConfig<TFields, Writable> & {
|
||||
type ResolvedCollectionConfig<
|
||||
TFields extends z.input<typeof collectionSchema>['fields'],
|
||||
Writable extends boolean,
|
||||
> = CollectionConfig<TFields, Writable> & {
|
||||
writable: Writable;
|
||||
};
|
||||
|
||||
|
@ -44,20 +52,20 @@ export function defineCollection<TFields extends z.input<typeof collectionSchema
|
|||
): ResolvedCollectionConfig<TFields, false> {
|
||||
return {
|
||||
...userConfig,
|
||||
writable: false
|
||||
writable: false,
|
||||
};
|
||||
}
|
||||
|
||||
export function defineWritableCollection<TFields extends z.input<typeof collectionSchema>['fields']>(
|
||||
userConfig: CollectionConfig<TFields, true>
|
||||
): ResolvedCollectionConfig<TFields, true> {
|
||||
export function defineWritableCollection<
|
||||
TFields extends z.input<typeof collectionSchema>['fields'],
|
||||
>(userConfig: CollectionConfig<TFields, true>): ResolvedCollectionConfig<TFields, true> {
|
||||
return {
|
||||
...userConfig,
|
||||
writable: true
|
||||
writable: true,
|
||||
};
|
||||
}
|
||||
|
||||
export type AstroConfigWithDB = z.infer<typeof astroConfigWithDBValidator>;
|
||||
export type AstroConfigWithDB = z.infer<typeof astroConfigWithDbSchema>;
|
||||
|
||||
type FieldOpts<T extends DBFieldInput> = Omit<T, 'type'>;
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
export const PACKAGE_NAME = JSON.parse(
|
||||
|
@ -13,6 +12,6 @@ export const DB_TYPES_FILE = 'db-types.d.ts';
|
|||
export const VIRTUAL_MODULE_ID = 'astro:db';
|
||||
|
||||
// TODO: copy DB to build for serverless
|
||||
export function getDbUrl(root: URL) {
|
||||
export function getLocalDbUrl(root: URL) {
|
||||
return new URL('.astro/content.db', root);
|
||||
}
|
||||
|
|
|
@ -2,11 +2,13 @@ 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';
|
||||
import { existsSync } from 'fs';
|
||||
import { rm } from 'fs/promises';
|
||||
import { getDbUrl } from './consts.js';
|
||||
import { getLocalDbUrl } from './consts.js';
|
||||
import { createDb, setupDbTables } from './internal.js';
|
||||
import { astroConfigWithDbSchema } from './config.js';
|
||||
import { getAstroStudioEnv, type VitePlugin } from './utils.js';
|
||||
import { appTokenError } from './errors.js';
|
||||
|
||||
export function integration(): AstroIntegration {
|
||||
return {
|
||||
|
@ -17,27 +19,33 @@ export function integration(): AstroIntegration {
|
|||
|
||||
// TODO: refine where we load collections
|
||||
// @matthewp: may want to load collections by path at runtime
|
||||
const collections = collectionsSchema.parse(config.db?.collections ?? {});
|
||||
const dbUrl = getDbUrl(config.root);
|
||||
if (existsSync(dbUrl)) {
|
||||
await rm(dbUrl);
|
||||
const configWithDb = astroConfigWithDbSchema.parse(config);
|
||||
const collections = configWithDb.db?.collections ?? {};
|
||||
const studio = configWithDb.db?.studio ?? false;
|
||||
|
||||
let dbPlugin: VitePlugin;
|
||||
if (studio && command === 'build') {
|
||||
const appToken = getAstroStudioEnv().ASTRO_STUDIO_APP_TOKEN;
|
||||
if (!appToken) {
|
||||
logger.error(appTokenError);
|
||||
process.exit(0);
|
||||
}
|
||||
dbPlugin = vitePluginDb({ connectToStudio: true, collections, appToken });
|
||||
} else {
|
||||
const dbUrl = getLocalDbUrl(config.root).href;
|
||||
if (existsSync(dbUrl)) {
|
||||
await rm(dbUrl);
|
||||
}
|
||||
const db = await createDb({ collections, dbUrl, seeding: true });
|
||||
await setupDbTables({ db, collections, logger });
|
||||
logger.info('Collections set up 🚀');
|
||||
|
||||
dbPlugin = vitePluginDb({ connectToStudio: false, collections, dbUrl });
|
||||
}
|
||||
const db = await createDb({ collections, dbUrl: dbUrl.href, seeding: true });
|
||||
await setupDbTables({ db, collections, logger });
|
||||
logger.info('Collections set up 🚀');
|
||||
|
||||
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),
|
||||
],
|
||||
plugins: [dbPlugin, vitePluginInjectEnvTs(config)],
|
||||
},
|
||||
});
|
||||
await typegen({ collections, root: config.root });
|
||||
|
|
|
@ -2,7 +2,6 @@ import type { SqliteRemoteDatabase } from 'drizzle-orm/sqlite-proxy';
|
|||
import { createClient } from '@libsql/client';
|
||||
import {
|
||||
type ReadableDBCollection,
|
||||
type WritableDBCollection,
|
||||
type BooleanField,
|
||||
type DBCollection,
|
||||
type DBCollections,
|
||||
|
@ -49,12 +48,18 @@ function checkIfModificationIsAllowed(collections: DBCollections, Table: SQLiteT
|
|||
// This totally works, don't worry about it
|
||||
const tableName = (Table as any)[(SQLiteTable as any).Symbol.Name];
|
||||
const collection = collections[tableName];
|
||||
if(collection.writable) {
|
||||
if (collection.writable) {
|
||||
throw new Error(`The [${tableName}] collection is read-only.`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function createDb({ collections, dbUrl, seeding }: {
|
||||
export { createDb as createStudioDb } from './cli/sync/remote-db.js';
|
||||
|
||||
export async function createDb({
|
||||
collections,
|
||||
dbUrl,
|
||||
seeding,
|
||||
}: {
|
||||
dbUrl: string;
|
||||
collections: DBCollections;
|
||||
seeding: boolean;
|
||||
|
@ -62,13 +67,9 @@ export async function createDb({ collections, dbUrl, seeding }: {
|
|||
const client = createClient({ url: dbUrl });
|
||||
const db = drizzle(client);
|
||||
|
||||
if(seeding) return db;
|
||||
if (seeding) return db;
|
||||
|
||||
const {
|
||||
insert: drizzleInsert,
|
||||
update: drizzleUpdate,
|
||||
delete: drizzleDelete
|
||||
} = db;
|
||||
const { insert: drizzleInsert, update: drizzleUpdate, delete: drizzleDelete } = db;
|
||||
return Object.assign(db, {
|
||||
insert(Table: SQLiteTable) {
|
||||
//console.log('Table info...', Table._);
|
||||
|
|
|
@ -1,17 +1,22 @@
|
|||
import { existsSync } from 'node:fs';
|
||||
import { DRIZZLE_MOD_IMPORT, INTERNAL_MOD_IMPORT, VIRTUAL_MODULE_ID, getDbUrl } from './consts.js';
|
||||
import { DRIZZLE_MOD_IMPORT, INTERNAL_MOD_IMPORT, VIRTUAL_MODULE_ID } from './consts.js';
|
||||
import type { DBCollections } from './types.js';
|
||||
import type { Plugin as VitePlugin } from 'vite';
|
||||
import type { VitePlugin } from './utils.js';
|
||||
|
||||
const resolvedVirtualModuleId = '\0' + VIRTUAL_MODULE_ID;
|
||||
|
||||
export function vitePluginDb({
|
||||
collections,
|
||||
root,
|
||||
}: {
|
||||
collections: DBCollections;
|
||||
root: URL;
|
||||
}): VitePlugin {
|
||||
export function vitePluginDb(
|
||||
params:
|
||||
| {
|
||||
connectToStudio: false;
|
||||
collections: DBCollections;
|
||||
dbUrl: string;
|
||||
}
|
||||
| {
|
||||
connectToStudio: true;
|
||||
collections: DBCollections;
|
||||
appToken: string;
|
||||
}
|
||||
): VitePlugin {
|
||||
return {
|
||||
name: 'astro:db',
|
||||
enforce: 'pre',
|
||||
|
@ -22,19 +27,22 @@ export function vitePluginDb({
|
|||
},
|
||||
load(id) {
|
||||
if (id !== resolvedVirtualModuleId) return;
|
||||
return getVirtualModContents({ collections, root });
|
||||
|
||||
if (params.connectToStudio) {
|
||||
return getStudioVirtualModContents(params);
|
||||
}
|
||||
return getVirtualModContents(params);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function getVirtualModContents({
|
||||
collections,
|
||||
root,
|
||||
dbUrl,
|
||||
}: {
|
||||
collections: DBCollections;
|
||||
root: URL;
|
||||
dbUrl: string;
|
||||
}) {
|
||||
const dbUrl = getDbUrl(root).href;
|
||||
return `
|
||||
import { collectionToTable, createDb } from ${INTERNAL_MOD_IMPORT};
|
||||
|
||||
|
@ -43,12 +51,32 @@ export const db = await createDb(${JSON.stringify({
|
|||
dbUrl,
|
||||
seeding: false,
|
||||
})});
|
||||
|
||||
export * from ${DRIZZLE_MOD_IMPORT};
|
||||
|
||||
${getStringifiedCollectionExports(collections)}
|
||||
`;
|
||||
}
|
||||
|
||||
export function getStudioVirtualModContents({
|
||||
collections,
|
||||
appToken,
|
||||
}: {
|
||||
collections: DBCollections;
|
||||
appToken: string;
|
||||
}) {
|
||||
return `
|
||||
import {collectionToTable, createStudioDb} from ${INTERNAL_MOD_IMPORT};
|
||||
|
||||
export const db = await createStudioDb(${JSON.stringify({
|
||||
appToken,
|
||||
})});
|
||||
export * from ${DRIZZLE_MOD_IMPORT};
|
||||
|
||||
${getStringifiedCollectionExports(collections)}
|
||||
`;
|
||||
}
|
||||
|
||||
function getStringifiedCollectionExports(collections: DBCollections) {
|
||||
return Object.entries(collections)
|
||||
.map(
|
||||
|
|
|
@ -4,8 +4,8 @@ import path from 'node:path';
|
|||
import { fileURLToPath } from 'node:url';
|
||||
import { bold, cyan } from 'kleur/colors';
|
||||
import { normalizePath } from 'vite';
|
||||
import type { Plugin as VitePlugin } from 'vite';
|
||||
import { DB_TYPES_FILE } from './consts.js';
|
||||
import type { VitePlugin } from './utils.js';
|
||||
|
||||
export function vitePluginInjectEnvTs({ srcDir, root }: { srcDir: URL; root: URL }): VitePlugin {
|
||||
return {
|
||||
|
|
Loading…
Add table
Reference in a new issue