diff options
author | 2025-06-05 16:18:32 +0200 | |
---|---|---|
committer | 2025-06-05 16:18:32 +0200 | |
commit | 61e779486fffd77eed4b95eec0ab5fcf106dd2d5 (patch) | |
tree | 85518de42eea4004e4c891676bce52836de06461 | |
parent | 0947a69192ad6820970902c7c951fb0cf31fcf4b (diff) | |
download | astro-feat/remove-studio.tar.gz astro-feat/remove-studio.tar.zst astro-feat/remove-studio.zip |
feat: remove studiofeat/remove-studio
32 files changed, 50 insertions, 1237 deletions
diff --git a/packages/astro/src/cli/index.ts b/packages/astro/src/cli/index.ts index 8511a8304..94c08d7b0 100644 --- a/packages/astro/src/cli/index.ts +++ b/packages/astro/src/cli/index.ts @@ -40,11 +40,6 @@ async function printAstroHelp() { ['preferences', 'Configure user preferences.'], ['telemetry', 'Configure telemetry settings.'], ], - 'Studio Commands': [ - ['login', 'Authenticate your machine with Astro Studio.'], - ['logout', 'End your authenticated session with Astro Studio.'], - ['link', 'Link this project directory to an Astro Studio project.'], - ], 'Global Flags': [ ['--config <path>', 'Specify your config file.'], ['--root <path>', 'Specify your project root folder.'], diff --git a/packages/astro/test/fixtures/ssr-prerender-chunks/deps/test-adapter/server.js b/packages/astro/test/fixtures/ssr-prerender-chunks/deps/test-adapter/server.js index 0921fe782..0515aaaa1 100644 --- a/packages/astro/test/fixtures/ssr-prerender-chunks/deps/test-adapter/server.js +++ b/packages/astro/test/fixtures/ssr-prerender-chunks/deps/test-adapter/server.js @@ -32,12 +32,6 @@ import { App } from 'astro/app'; request.headers.get('cf-connecting-ip') ); - process.env.ASTRO_STUDIO_APP_TOKEN ??= (() => { - if (typeof env.ASTRO_STUDIO_APP_TOKEN === 'string') { - return env.ASTRO_STUDIO_APP_TOKEN; - } - })(); - const locals = { runtime: { env: env, diff --git a/packages/db/package.json b/packages/db/package.json index 5d0b25efd..a10435695 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,7 +1,7 @@ { "name": "@astrojs/db", "version": "0.15.0", - "description": "Add libSQL and Astro Studio support to your Astro site", + "description": "Add libSQL support to your Astro site", "license": "MIT", "repository": { "type": "git", @@ -69,7 +69,6 @@ "test": "astro-scripts test \"test/**/*.test.js\"" }, "dependencies": { - "@astrojs/studio": "workspace:*", "@libsql/client": "^0.15.2", "async-listen": "^3.1.0", "deep-diff": "^1.0.2", diff --git a/packages/db/src/core/cli/commands/execute/index.ts b/packages/db/src/core/cli/commands/execute/index.ts index 053736291..bf3dfd523 100644 --- a/packages/db/src/core/cli/commands/execute/index.ts +++ b/packages/db/src/core/cli/commands/execute/index.ts @@ -11,7 +11,7 @@ import { } from '../../../errors.js'; import { getLocalVirtualModContents, - getStudioVirtualModContents, + getRemoteVirtualModContents, } from '../../../integration/vite-plugin-db.js'; import { bundleFile, importBundledFile } from '../../../load-file.js'; import type { DBConfig } from '../../../types.js'; @@ -40,10 +40,9 @@ export async function cmd({ let virtualModContents: string; if (flags.remote) { - const appToken = await getManagedRemoteToken(flags.token); - virtualModContents = getStudioVirtualModContents({ + virtualModContents = getRemoteVirtualModContents({ tables: dbConfig.tables ?? {}, - appToken: appToken.token, + appToken: getManagedRemoteToken(flags.token), isBuild: false, output: 'server', }); diff --git a/packages/db/src/core/cli/commands/link/index.ts b/packages/db/src/core/cli/commands/link/index.ts deleted file mode 100644 index 4a105df9d..000000000 --- a/packages/db/src/core/cli/commands/link/index.ts +++ /dev/null @@ -1,295 +0,0 @@ -import { mkdir, writeFile } from 'node:fs/promises'; -import { homedir } from 'node:os'; -import { basename } from 'node:path'; -import { - MISSING_SESSION_ID_ERROR, - PROJECT_ID_FILE, - getAstroStudioUrl, - getSessionIdFromFile, -} from '@astrojs/studio'; -import { slug } from 'github-slugger'; -import { bgRed, cyan } from 'kleur/colors'; -import prompts from 'prompts'; -import yoctoSpinner from 'yocto-spinner'; -import { safeFetch } from '../../../../runtime/utils.js'; -import type { Result } from '../../../utils.js'; - -export async function cmd() { - const sessionToken = await getSessionIdFromFile(); - if (!sessionToken) { - console.error(MISSING_SESSION_ID_ERROR); - process.exit(1); - } - await promptBegin(); - const isLinkExisting = await promptLinkExisting(); - if (isLinkExisting) { - const workspaceId = await promptWorkspace(sessionToken); - const existingProjectData = await promptExistingProjectName({ workspaceId }); - return await linkProject(existingProjectData.id); - } - - const isLinkNew = await promptLinkNew(); - if (isLinkNew) { - const workspaceId = await promptWorkspace(sessionToken); - const newProjectName = await promptNewProjectName(); - const newProjectRegion = await promptNewProjectRegion(); - const spinner = yoctoSpinner({ text: 'Creating new project...' }).start(); - const newProjectData = await createNewProject({ - workspaceId, - name: newProjectName, - region: newProjectRegion, - }); - // TODO(fks): Actually listen for project creation before continuing - // This is just a dumb spinner that roughly matches database creation time. - await new Promise((r) => setTimeout(r, 4000)); - spinner.success('Project created!'); - return await linkProject(newProjectData.id); - } -} - -async function linkProject(id: string) { - await mkdir(new URL('.', PROJECT_ID_FILE), { recursive: true }); - await writeFile(PROJECT_ID_FILE, `${id}`); - console.info('Project linked.'); -} - -async function getWorkspaces(sessionToken: string) { - const linkUrl = new URL(getAstroStudioUrl() + '/api/cli/workspaces.list'); - const response = await safeFetch( - linkUrl, - { - method: 'POST', - headers: { - Authorization: `Bearer ${sessionToken}`, - 'Content-Type': 'application/json', - }, - }, - (res) => { - // Unauthorized - if (res.status === 401) { - throw new Error( - `${bgRed('Unauthorized')}\n\n Are you logged in?\n Run ${cyan( - 'astro login', - )} to authenticate and then try linking again.\n\n`, - ); - } - throw new Error(`Failed to fetch user workspace: ${res.status} ${res.statusText}`); - }, - ); - - const { data, success } = (await response.json()) as Result<{ id: string; name: string }[]>; - if (!success) { - throw new Error(`Failed to fetch user's workspace.`); - } - return data; -} - -/** - * Get the workspace ID to link to. - * Prompts the user to choose if they have more than one workspace in Astro Studio. - * @returns A `Promise` for the workspace ID to use. - */ -async function promptWorkspace(sessionToken: string) { - const workspaces = await getWorkspaces(sessionToken); - if (workspaces.length === 0) { - console.error('No workspaces found.'); - process.exit(1); - } - - if (workspaces.length === 1) { - return workspaces[0].id; - } - - const { workspaceId } = await prompts({ - type: 'autocomplete', - name: 'workspaceId', - message: 'Select your workspace:', - limit: 5, - choices: workspaces.map((w) => ({ title: w.name, value: w.id })), - }); - if (typeof workspaceId !== 'string') { - console.log('Canceled.'); - process.exit(0); - } - return workspaceId; -} - -async function createNewProject({ - workspaceId, - name, - region, -}: { - workspaceId: string; - name: string; - region: string; -}) { - const linkUrl = new URL(getAstroStudioUrl() + '/api/cli/projects.create'); - const response = await safeFetch( - linkUrl, - { - method: 'POST', - headers: { - Authorization: `Bearer ${await getSessionIdFromFile()}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ workspaceId, name, region }), - }, - (res) => { - // Unauthorized - if (res.status === 401) { - console.error( - `${bgRed('Unauthorized')}\n\n Are you logged in?\n Run ${cyan( - 'astro login', - )} to authenticate and then try linking again.\n\n`, - ); - process.exit(1); - } - console.error(`Failed to create project: ${res.status} ${res.statusText}`); - process.exit(1); - }, - ); - - const { data, success } = (await response.json()) as Result<{ id: string; idName: string }>; - if (!success) { - console.error(`Failed to create project.`); - process.exit(1); - } - return { id: data.id, idName: data.idName }; -} - -async function promptExistingProjectName({ workspaceId }: { workspaceId: string }) { - const linkUrl = new URL(getAstroStudioUrl() + '/api/cli/projects.list'); - const response = await safeFetch( - linkUrl, - { - method: 'POST', - headers: { - Authorization: `Bearer ${await getSessionIdFromFile()}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ workspaceId }), - }, - (res) => { - if (res.status === 401) { - console.error( - `${bgRed('Unauthorized')}\n\n Are you logged in?\n Run ${cyan( - 'astro login', - )} to authenticate and then try linking again.\n\n`, - ); - process.exit(1); - } - console.error(`Failed to fetch projects: ${res.status} ${res.statusText}`); - process.exit(1); - }, - ); - - const { data, success } = (await response.json()) as Result< - { id: string; name: string; idName: string }[] - >; - if (!success) { - console.error(`Failed to fetch projects.`); - process.exit(1); - } - const { projectId } = await prompts({ - type: 'autocomplete', - name: 'projectId', - message: 'What is your project name?', - limit: 5, - choices: data.map((p) => ({ title: p.name, value: p.id })), - }); - if (typeof projectId !== 'string') { - console.log('Canceled.'); - process.exit(0); - } - const selectedProjectData = data.find((p: any) => p.id === projectId)!; - return selectedProjectData; -} - -async function promptBegin(): Promise<void> { - // Get the current working directory relative to the user's home directory - const prettyCwd = process.cwd().replace(homedir(), '~'); - - // prompt - const { begin } = await prompts({ - type: 'confirm', - name: 'begin', - message: `Link "${prettyCwd}" with Astro Studio?`, - initial: true, - }); - if (!begin) { - console.log('Canceled.'); - process.exit(0); - } -} - -/** - * Ask the user if they want to link to an existing Astro Studio project. - * @returns A `Promise` for the user’s answer: `true` if they answer yes, otherwise `false`. - */ -async function promptLinkExisting(): Promise<boolean> { - // prompt - const { linkExisting } = await prompts({ - type: 'confirm', - name: 'linkExisting', - message: `Link with an existing project in Astro Studio?`, - initial: true, - }); - return !!linkExisting; -} - -/** - * Ask the user if they want to link to a new Astro Studio Project. - * **Exits the process if they answer no.** - * @returns A `Promise` for the user’s answer: `true` if they answer yes. - */ -async function promptLinkNew(): Promise<boolean> { - // prompt - const { linkNew } = await prompts({ - type: 'confirm', - name: 'linkNew', - message: `Create a new project in Astro Studio?`, - initial: true, - }); - if (!linkNew) { - console.log('Canceled.'); - process.exit(0); - } - return true; -} - -async function promptNewProjectName(): Promise<string> { - const { newProjectName } = await prompts({ - type: 'text', - name: 'newProjectName', - message: `What is your new project's name?`, - initial: basename(process.cwd()), - format: (val) => slug(val), - }); - if (!newProjectName) { - console.log('Canceled.'); - process.exit(0); - } - return newProjectName; -} - -async function promptNewProjectRegion(): Promise<string> { - const { newProjectRegion } = await prompts({ - type: 'select', - name: 'newProjectRegion', - message: `Where should your new database live?`, - choices: [ - { title: 'North America (East)', value: 'NorthAmericaEast' }, - { title: 'North America (West)', value: 'NorthAmericaWest' }, - { title: 'Europe (Amsterdam)', value: 'EuropeCentral' }, - { title: 'South America (Brazil)', value: 'SouthAmericaEast' }, - { title: 'Asia (India)', value: 'AsiaSouth' }, - { title: 'Asia (Japan)', value: 'AsiaNorthEast' }, - ], - initial: 0, - }); - if (!newProjectRegion) { - console.log('Canceled.'); - process.exit(0); - } - return newProjectRegion; -} diff --git a/packages/db/src/core/cli/commands/login/index.ts b/packages/db/src/core/cli/commands/login/index.ts deleted file mode 100644 index 0b0979384..000000000 --- a/packages/db/src/core/cli/commands/login/index.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { mkdir, writeFile } from 'node:fs/promises'; -import { createServer as _createServer } from 'node:http'; -import { SESSION_LOGIN_FILE, getAstroStudioUrl } from '@astrojs/studio'; -import type { AstroConfig } from 'astro'; -import { listen } from 'async-listen'; -import { cyan } from 'kleur/colors'; -import open from 'open'; -import prompt from 'prompts'; -import type { Arguments } from 'yargs-parser'; -import yoctoSpinner from 'yocto-spinner'; -import type { DBConfig } from '../../../types.js'; - -const isWebContainer = - // Stackblitz heuristic - process.versions?.webcontainer ?? - // GitHub Codespaces heuristic - process.env.CODESPACE_NAME; - -export async function cmd({ - flags, -}: { - astroConfig: AstroConfig; - dbConfig: DBConfig; - flags: Arguments; -}) { - let session = flags.session; - - if (!session && isWebContainer) { - console.log(`Please visit the following URL in your web browser:`); - console.log(cyan(`${getAstroStudioUrl()}/auth/cli/login`)); - console.log(`After login in complete, enter the verification code displayed:`); - const response = await prompt({ - type: 'text', - name: 'session', - message: 'Verification code:', - }); - if (!response.session) { - console.error('Cancelling login.'); - process.exit(0); - } - session = response.session; - console.log('Successfully logged in'); - } else if (!session) { - const { url, promise } = await createServer(); - const loginUrl = new URL('/auth/cli/login', getAstroStudioUrl()); - loginUrl.searchParams.set('returnTo', url); - console.log(`Opening the following URL in your browser...`); - console.log(cyan(loginUrl.href)); - console.log(`If something goes wrong, copy-and-paste the URL into your browser.`); - open(loginUrl.href); - const spinner = yoctoSpinner({ text: 'Waiting for confirmation...' }); - session = await promise; - spinner.success('Successfully logged in'); - } - - await mkdir(new URL('.', SESSION_LOGIN_FILE), { recursive: true }); - await writeFile(SESSION_LOGIN_FILE, `${session}`); -} - -// NOTE(fks): How the Astro CLI login process works: -// 1. The Astro CLI creates a temporary server to listen for the session token -// 2. The user is directed to studio.astro.build/ to login -// 3. The user is redirected back to the temporary server with their session token -// 4. The temporary server receives and saves the session token, logging the user in -// 5. The user is redirected one last time to a success/failure page -async function createServer(): Promise<{ url: string; promise: Promise<string> }> { - let resolve: (value: string | PromiseLike<string>) => void, reject: (reason?: Error) => void; - - const server = _createServer((req, res) => { - // Handle the request - const url = new URL(req.url ?? '/', `http://${req.headers.host}`); - const sessionParam = url.searchParams.get('session'); - // Handle the response & resolve the promise - res.statusCode = 302; - if (!sessionParam) { - res.setHeader('location', getAstroStudioUrl() + '/auth/cli/error'); - reject(new Error('Failed to log in')); - } else { - res.setHeader('location', getAstroStudioUrl() + '/auth/cli/success'); - resolve(sessionParam); - } - res.end(); - }); - - const { port } = await listen(server, 0, '127.0.0.1'); - const serverUrl = `http://localhost:${port}`; - const sessionPromise = new Promise<string>((_resolve, _reject) => { - resolve = _resolve; - reject = _reject; - }).finally(() => { - server.closeAllConnections(); - server.close(); - }); - - return { url: serverUrl, promise: sessionPromise }; -} diff --git a/packages/db/src/core/cli/commands/logout/index.ts b/packages/db/src/core/cli/commands/logout/index.ts deleted file mode 100644 index 8b7878659..000000000 --- a/packages/db/src/core/cli/commands/logout/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { unlink } from 'node:fs/promises'; -import { SESSION_LOGIN_FILE } from '@astrojs/studio'; - -export async function cmd() { - await unlink(SESSION_LOGIN_FILE); - console.log('Successfully logged out of Astro Studio.'); -} diff --git a/packages/db/src/core/cli/commands/push/index.ts b/packages/db/src/core/cli/commands/push/index.ts index 590d4f06e..46a457884 100644 --- a/packages/db/src/core/cli/commands/push/index.ts +++ b/packages/db/src/core/cli/commands/push/index.ts @@ -3,12 +3,10 @@ import { sql } from 'drizzle-orm'; import prompts from 'prompts'; import type { Arguments } from 'yargs-parser'; import { createRemoteDatabaseClient } from '../../../../runtime/index.js'; -import { safeFetch } from '../../../../runtime/utils.js'; import { MIGRATION_VERSION } from '../../../consts.js'; import type { DBConfig, DBSnapshot } from '../../../types.js'; import { type RemoteDatabaseInfo, - type Result, getManagedRemoteToken, getRemoteDatabaseInfo, } from '../../../utils.js'; @@ -31,10 +29,10 @@ export async function cmd({ const isDryRun = flags.dryRun; const isForceReset = flags.forceReset; const dbInfo = getRemoteDatabaseInfo(); - const appToken = await getManagedRemoteToken(flags.token, dbInfo); + const appToken = getManagedRemoteToken(flags.token); const productionSnapshot = await getProductionCurrentSnapshot({ dbInfo, - appToken: appToken.token, + appToken: appToken, }); const currentSnapshot = createCurrentSnapshot(dbConfig); const isFromScratch = !productionSnapshot; @@ -77,13 +75,11 @@ export async function cmd({ await pushSchema({ statements: migrationQueries, dbInfo, - appToken: appToken.token, + appToken: appToken, isDryRun, currentSnapshot: currentSnapshot, }); } - // cleanup and exit - await appToken.destroy(); console.info('Push complete!'); } @@ -110,9 +106,7 @@ async function pushSchema({ return new Response(null, { status: 200 }); } - return dbInfo.type === 'studio' - ? pushToStudio(requestBody, appToken, dbInfo.url) - : pushToDb(requestBody, appToken, dbInfo.url); + return pushToDb(requestBody, appToken, dbInfo.url); } type RequestBody = { @@ -145,29 +139,3 @@ async function pushToDb(requestBody: RequestBody, appToken: string, remoteUrl: s )`); }); } - -async function pushToStudio(requestBody: RequestBody, appToken: string, remoteUrl: string) { - const url = new URL('/db/push', remoteUrl); - const response = await safeFetch( - url, - { - method: 'POST', - headers: new Headers({ - Authorization: `Bearer ${appToken}`, - }), - body: JSON.stringify(requestBody), - }, - async (res) => { - console.error(`${url.toString()} failed: ${res.status} ${res.statusText}`); - console.error(await res.text()); - throw new Error(`/db/push fetch failed: ${res.status} ${res.statusText}`); - }, - ); - - const result = (await response.json()) as Result<never>; - if (!result.success) { - console.error(`${url.toString()} unsuccessful`); - console.error(await response.text()); - throw new Error(`/db/push fetch unsuccessful`); - } -} diff --git a/packages/db/src/core/cli/commands/shell/index.ts b/packages/db/src/core/cli/commands/shell/index.ts index dcc54fc70..278830b4e 100644 --- a/packages/db/src/core/cli/commands/shell/index.ts +++ b/packages/db/src/core/cli/commands/shell/index.ts @@ -26,14 +26,12 @@ export async function cmd({ } const dbInfo = getRemoteDatabaseInfo(); if (flags.remote) { - const appToken = await getManagedRemoteToken(flags.token, dbInfo); const db = createRemoteDatabaseClient({ dbType: dbInfo.type, remoteUrl: dbInfo.url, - appToken: appToken.token, + appToken: getManagedRemoteToken(flags.token), }); const result = await db.run(sql.raw(query)); - await appToken.destroy(); console.log(result); } else { const { ASTRO_DATABASE_FILE } = getAstroEnv(); diff --git a/packages/db/src/core/cli/commands/verify/index.ts b/packages/db/src/core/cli/commands/verify/index.ts index 35f489a80..6499c272e 100644 --- a/packages/db/src/core/cli/commands/verify/index.ts +++ b/packages/db/src/core/cli/commands/verify/index.ts @@ -20,10 +20,9 @@ export async function cmd({ }) { const isJson = flags.json; const dbInfo = getRemoteDatabaseInfo(); - const appToken = await getManagedRemoteToken(flags.token, dbInfo); const productionSnapshot = await getProductionCurrentSnapshot({ dbInfo, - appToken: appToken.token, + appToken: getManagedRemoteToken(flags.token), }); const currentSnapshot = createCurrentSnapshot(dbConfig); const { queries: migrationQueries, confirmations } = await getMigrationQueries({ @@ -53,6 +52,5 @@ export async function cmd({ console.log(result.message); } - await appToken.destroy(); process.exit(result.exitCode); } diff --git a/packages/db/src/core/cli/index.ts b/packages/db/src/core/cli/index.ts index 531b016a6..6c54c2ae6 100644 --- a/packages/db/src/core/cli/index.ts +++ b/packages/db/src/core/cli/index.ts @@ -41,18 +41,6 @@ export async function cli({ const { cmd } = await import('./commands/execute/index.js'); return await cmd({ astroConfig, dbConfig, flags }); } - case 'login': { - const { cmd } = await import('./commands/login/index.js'); - return await cmd({ astroConfig, dbConfig, flags }); - } - case 'logout': { - const { cmd } = await import('./commands/logout/index.js'); - return await cmd(); - } - case 'link': { - const { cmd } = await import('./commands/link/index.js'); - return await cmd(); - } default: { if (command != null) { console.error(`Unknown command: ${command}`); @@ -63,15 +51,15 @@ export async function cli({ headline: ' ', tables: { Commands: [ - ['push', 'Push table schema updates to Astro Studio.'], - ['verify', 'Test schema updates /w Astro Studio (good for CI).'], + ['push', 'Push table schema updates to libSQL.'], + ['verify', 'Test schema updates /w libSQL (good for CI).'], [ 'astro db execute <file-path>', - 'Execute a ts/js file using astro:db. Use --remote to connect to Studio.', + 'Execute a ts/js file using astro:db. Use --remote to connect to libSQL.', ], [ 'astro db shell --query <sql-string>', - 'Execute a SQL string. Use --remote to connect to Studio.', + 'Execute a SQL string. Use --remote to connect to libSQL.', ], ], }, diff --git a/packages/db/src/core/cli/migration-queries.ts b/packages/db/src/core/cli/migration-queries.ts index db3972d09..0f369e684 100644 --- a/packages/db/src/core/cli/migration-queries.ts +++ b/packages/db/src/core/cli/migration-queries.ts @@ -7,7 +7,7 @@ import { customAlphabet } from 'nanoid'; import { hasPrimaryKey } from '../../runtime/index.js'; import { createRemoteDatabaseClient } from '../../runtime/index.js'; import { isSerializedSQL } from '../../runtime/types.js'; -import { isDbError, safeFetch } from '../../runtime/utils.js'; +import { isDbError } from '../../runtime/utils.js'; import { MIGRATION_VERSION } from '../consts.js'; import { RENAME_COLUMN_ERROR, RENAME_TABLE_ERROR } from '../errors.js'; import { @@ -35,7 +35,7 @@ import type { ResolvedIndexes, TextColumn, } from '../types.js'; -import type { RemoteDatabaseInfo, Result } from '../utils.js'; +import type { RemoteDatabaseInfo } from '../utils.js'; const sqlite = new SQLiteAsyncDialect(); const genTempTableName = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10); @@ -428,9 +428,7 @@ export function getProductionCurrentSnapshot(options: { dbInfo: RemoteDatabaseInfo; appToken: string; }): Promise<DBSnapshot | undefined> { - return options.dbInfo.type === 'studio' - ? getStudioCurrentSnapshot(options.appToken, options.dbInfo.url) - : getDbCurrentSnapshot(options.appToken, options.dbInfo.url); + return getDbCurrentSnapshot(options.appToken, options.dbInfo.url); } async function getDbCurrentSnapshot( @@ -473,36 +471,6 @@ async function getDbCurrentSnapshot( } } -async function getStudioCurrentSnapshot( - appToken: string, - remoteUrl: string, -): Promise<DBSnapshot | undefined> { - const url = new URL('/db/schema', remoteUrl); - - const response = await safeFetch( - url, - { - method: 'POST', - headers: new Headers({ - Authorization: `Bearer ${appToken}`, - }), - }, - async (res) => { - console.error(`${url.toString()} failed: ${res.status} ${res.statusText}`); - console.error(await res.text()); - throw new Error(`/db/schema fetch failed: ${res.status} ${res.statusText}`); - }, - ); - - const result = (await response.json()) as Result<DBSnapshot>; - if (!result.success) { - console.error(`${url.toString()} unsuccessful`); - console.error(await response.text()); - throw new Error(`/db/schema fetch unsuccessful`); - } - return result.data; -} - function getDropTableQueriesForSnapshot(snapshot: DBSnapshot) { const queries = []; for (const tableName of Object.keys(snapshot.schema)) { diff --git a/packages/db/src/core/integration/index.ts b/packages/db/src/core/integration/index.ts index c6f58d2fd..bc038c7d2 100644 --- a/packages/db/src/core/integration/index.ts +++ b/packages/db/src/core/integration/index.ts @@ -2,7 +2,6 @@ import { existsSync } from 'node:fs'; import { mkdir, writeFile } from 'node:fs/promises'; import { dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; -import type { ManagedAppToken } from '@astrojs/studio'; import type { AstroIntegration } from 'astro'; import { blue, yellow } from 'kleur/colors'; import { @@ -33,7 +32,6 @@ function astroDBIntegration(): AstroIntegration { let connectToRemote = false; let configFileDependencies: string[] = []; let root: URL; - let appToken: ManagedAppToken | undefined; // Used during production builds to load seed files. let tempViteServer: ViteDevServer | undefined; @@ -72,10 +70,9 @@ function astroDBIntegration(): AstroIntegration { connectToRemote = process.env.ASTRO_INTERNAL_TEST_REMOTE || args['remote']; if (connectToRemote) { - appToken = await getManagedRemoteToken(); dbPlugin = vitePluginDb({ - connectToStudio: connectToRemote, - appToken: appToken.token, + connectToRemote: connectToRemote, + appToken: getManagedRemoteToken(), tables, root: config.root, srcDir: config.srcDir, @@ -84,7 +81,7 @@ function astroDBIntegration(): AstroIntegration { }); } else { dbPlugin = vitePluginDb({ - connectToStudio: false, + connectToRemote: false, tables, seedFiles, root: config.root, @@ -161,7 +158,7 @@ function astroDBIntegration(): AstroIntegration { if (!connectToRemote && !databaseFileEnvDefined() && finalBuildOutput === 'server') { const message = `Attempting to build without the --remote flag or the ASTRO_DATABASE_FILE environment variable defined. You probably want to pass --remote to astro build.`; const hint = - 'Learn more connecting to Studio: https://docs.astro.build/en/guides/astro-db/#connect-to-astro-studio'; + 'Learn more connecting to libSQL: https://docs.astro.build/en/guides/astro-db/#connect-a-libsql-database-for-production'; throw new AstroDbError(message, hint); } @@ -174,7 +171,6 @@ function astroDBIntegration(): AstroIntegration { }; }, 'astro:build:done': async ({}) => { - await appToken?.destroy(); await tempViteServer?.close(); }, }, diff --git a/packages/db/src/core/integration/vite-plugin-db.ts b/packages/db/src/core/integration/vite-plugin-db.ts index 410d49157..7a7173bfc 100644 --- a/packages/db/src/core/integration/vite-plugin-db.ts +++ b/packages/db/src/core/integration/vite-plugin-db.ts @@ -34,7 +34,7 @@ export type SeedHandler = { type VitePluginDBParams = | { - connectToStudio: false; + connectToRemote: false; tables: LateTables; seedFiles: LateSeedFiles; srcDir: URL; @@ -44,7 +44,7 @@ type VitePluginDBParams = seedHandler: SeedHandler; } | { - connectToStudio: true; + connectToRemote: true; tables: LateTables; appToken: string; srcDir: URL; @@ -71,8 +71,8 @@ export function vitePluginDb(params: VitePluginDBParams): VitePlugin { async load(id) { if (id !== resolved.module && id !== resolved.importedFromSeedFile) return; - if (params.connectToStudio) { - return getStudioVirtualModContents({ + if (params.connectToRemote) { + return getRemoteVirtualModContents({ appToken: params.appToken, tables: params.tables.get(), isBuild: command === 'build', @@ -138,7 +138,7 @@ export * from ${RUNTIME_VIRTUAL_IMPORT}; ${getStringifiedTableExports(tables)}`; } -export function getStudioVirtualModContents({ +export function getRemoteVirtualModContents({ tables, appToken, isBuild, @@ -153,13 +153,12 @@ export function getStudioVirtualModContents({ function appTokenArg() { if (isBuild) { - const envPrefix = dbInfo.type === 'studio' ? 'ASTRO_STUDIO' : 'ASTRO_DB'; if (output === 'server') { // In production build, always read the runtime environment variable. - return `process.env.${envPrefix}_APP_TOKEN`; + return `process.env.ASTRO_DB_APP_TOKEN`; } else { // Static mode or prerendering needs the local app token. - return `process.env.${envPrefix}_APP_TOKEN ?? ${JSON.stringify(appToken)}`; + return `process.env.ASTRO_DB_APP_TOKEN ?? ${JSON.stringify(appToken)}`; } } else { return JSON.stringify(appToken); @@ -171,9 +170,7 @@ export function getStudioVirtualModContents({ if (isBuild) { // Allow overriding, mostly for testing - return dbInfo.type === 'studio' - ? `import.meta.env.ASTRO_STUDIO_REMOTE_DB_URL ?? ${dbStr}` - : `import.meta.env.ASTRO_DB_REMOTE_URL ?? ${dbStr}`; + return `import.meta.env.ASTRO_DB_REMOTE_URL ?? ${dbStr}`; } else { return dbStr; } diff --git a/packages/db/src/core/load-file.ts b/packages/db/src/core/load-file.ts index 027deaa60..f7a4226b6 100644 --- a/packages/db/src/core/load-file.ts +++ b/packages/db/src/core/load-file.ts @@ -144,8 +144,8 @@ export async function bundleFile({ format: 'esm', sourcemap: 'inline', metafile: true, + // TODO: use astro:env define: { - 'import.meta.env.ASTRO_STUDIO_REMOTE_DB_URL': 'undefined', 'import.meta.env.ASTRO_DB_REMOTE_DB_URL': 'undefined', 'import.meta.env.ASTRO_DATABASE_FILE': JSON.stringify(ASTRO_DATABASE_FILE ?? ''), }, diff --git a/packages/db/src/core/schemas.ts b/packages/db/src/core/schemas.ts index c9575a79a..1147538ce 100644 --- a/packages/db/src/core/schemas.ts +++ b/packages/db/src/core/schemas.ts @@ -94,7 +94,7 @@ const textColumnBaseSchema = baseColumnSchema z.object({ // text primary key allows NULL values. // NULL values bypass unique checks, which could - // lead to duplicate URLs per record in Astro Studio. + // lead to duplicate URLs per record. // disable `optional` for primary keys. primaryKey: z.literal(true), optional: z.literal(false).optional(), diff --git a/packages/db/src/core/utils.ts b/packages/db/src/core/utils.ts index b246997e2..3348e90a0 100644 --- a/packages/db/src/core/utils.ts +++ b/packages/db/src/core/utils.ts @@ -1,4 +1,3 @@ -import { type ManagedAppToken, getAstroStudioEnv, getManagedAppTokenOrExit } from '@astrojs/studio'; import type { AstroConfig, AstroIntegration } from 'astro'; import { loadEnv } from 'vite'; import './types.js'; @@ -11,49 +10,23 @@ export function getAstroEnv(envMode = ''): Record<`ASTRO_${string}`, string> { } export type RemoteDatabaseInfo = { - type: 'libsql' | 'studio'; + type: 'libsql'; url: string; }; export function getRemoteDatabaseInfo(): RemoteDatabaseInfo { const astroEnv = getAstroEnv(); - const studioEnv = getAstroStudioEnv(); - - if (studioEnv.ASTRO_STUDIO_REMOTE_DB_URL) - return { - type: 'studio', - url: studioEnv.ASTRO_STUDIO_REMOTE_DB_URL, - }; - - if (astroEnv.ASTRO_DB_REMOTE_URL) - return { - type: 'libsql', - url: astroEnv.ASTRO_DB_REMOTE_URL, - }; return { - type: 'studio', - url: 'https://db.services.astro.build', + type: 'libsql', + url: astroEnv.ASTRO_DB_REMOTE_URL, }; } -export function getManagedRemoteToken( - token?: string, - dbInfo?: RemoteDatabaseInfo, -): Promise<ManagedAppToken> { - dbInfo ??= getRemoteDatabaseInfo(); - - if (dbInfo.type === 'studio') { - return getManagedAppTokenOrExit(token); - } - +export function getManagedRemoteToken(token?: string): string { const astroEnv = getAstroEnv(); - return Promise.resolve({ - token: token ?? astroEnv.ASTRO_DB_APP_TOKEN, - renew: () => Promise.resolve(), - destroy: () => Promise.resolve(), - }); + return token ?? astroEnv.ASTRO_DB_APP_TOKEN; } export function getDbDirectoryUrl(root: URL | string) { diff --git a/packages/db/src/runtime/db-client.ts b/packages/db/src/runtime/db-client.ts index 55288951d..e45a2d717 100644 --- a/packages/db/src/runtime/db-client.ts +++ b/packages/db/src/runtime/db-client.ts @@ -1,10 +1,7 @@ -import type { InStatement } from '@libsql/client'; import { type Config as LibSQLConfig, createClient } from '@libsql/client'; import type { LibSQLDatabase } from 'drizzle-orm/libsql'; import { drizzle as drizzleLibsql } from 'drizzle-orm/libsql'; -import { type SqliteRemoteDatabase, drizzle as drizzleProxy } from 'drizzle-orm/sqlite-proxy'; -import { z } from 'zod'; -import { DetailedLibsqlError, safeFetch } from './utils.js'; +import type { SqliteRemoteDatabase } from 'drizzle-orm/sqlite-proxy'; const isWebContainer = !!process.versions?.webcontainer; @@ -34,16 +31,8 @@ export function createLocalDatabaseClient(options: LocalDbClientOptions): LibSQL return db; } -const remoteResultSchema = z.object({ - columns: z.array(z.string()), - columnTypes: z.array(z.string()), - rows: z.array(z.array(z.unknown())), - rowsAffected: z.number(), - lastInsertRowid: z.unknown().optional(), -}); - type RemoteDbClientOptions = { - dbType: 'studio' | 'libsql'; + dbType: 'libsql'; appToken: string; remoteUrl: string | URL; }; @@ -51,9 +40,7 @@ type RemoteDbClientOptions = { export function createRemoteDatabaseClient(options: RemoteDbClientOptions) { const remoteUrl = new URL(options.remoteUrl); - return options.dbType === 'studio' - ? createStudioDatabaseClient(options.appToken, remoteUrl) - : createRemoteLibSQLClient(options.appToken, remoteUrl, options.remoteUrl.toString()); + return createRemoteLibSQLClient(options.appToken, remoteUrl, options.remoteUrl.toString()); } // this function parses the options from a `Record<string, string>` @@ -99,167 +86,3 @@ function createRemoteLibSQLClient(appToken: string, remoteDbURL: URL, rawUrl: st const client = createClient({ ...parseOpts(options), url, authToken: appToken }); return drizzleLibsql(client); } - -function createStudioDatabaseClient(appToken: string, remoteDbURL: URL) { - if (appToken == null) { - throw new Error(`Cannot create a remote client: missing app token.`); - } - - const url = new URL('/db/query', remoteDbURL); - - const db = drizzleProxy( - async (sql, parameters, method) => { - const requestBody: InStatement = { sql, args: parameters }; - const res = await safeFetch( - url, - { - method: 'POST', - headers: { - Authorization: `Bearer ${appToken}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(requestBody), - }, - async (response) => { - throw await parseRemoteError(response); - }, - ); - - let remoteResult: z.infer<typeof remoteResultSchema>; - try { - const json = await res.json(); - remoteResult = remoteResultSchema.parse(json); - } catch { - throw new DetailedLibsqlError({ - message: await getUnexpectedResponseMessage(res), - code: KNOWN_ERROR_CODES.SQL_QUERY_FAILED, - }); - } - - if (method === 'run') { - const rawRows = Array.from(remoteResult.rows); - // Implement basic `toJSON()` for Drizzle to serialize properly - (remoteResult as any).rows.toJSON = () => rawRows; - // Using `db.run()` drizzle massages the rows into an object. - // So in order to make dev/prod consistent, we need to do the same here. - // This creates an object and loops over each row replacing it with the object. - for (let i = 0; i < remoteResult.rows.length; i++) { - let row = remoteResult.rows[i]; - let item: Record<string, any> = {}; - remoteResult.columns.forEach((col, index) => { - item[col] = row[index]; - }); - (remoteResult as any).rows[i] = item; - } - return remoteResult; - } - - // Drizzle expects each row as an array of its values - const rowValues: unknown[][] = []; - - for (const row of remoteResult.rows) { - if (row != null && typeof row === 'object') { - rowValues.push(Object.values(row)); - } - } - - if (method === 'get') { - return { rows: rowValues[0] }; - } - - return { rows: rowValues }; - }, - async (queries) => { - const stmts: InStatement[] = queries.map(({ sql, params }) => ({ sql, args: params })); - const res = await safeFetch( - url, - { - method: 'POST', - headers: { - Authorization: `Bearer ${appToken}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(stmts), - }, - async (response) => { - throw await parseRemoteError(response); - }, - ); - - let remoteResults: z.infer<typeof remoteResultSchema>[]; - try { - const json = await res.json(); - remoteResults = z.array(remoteResultSchema).parse(json); - } catch { - throw new DetailedLibsqlError({ - message: await getUnexpectedResponseMessage(res), - code: KNOWN_ERROR_CODES.SQL_QUERY_FAILED, - }); - } - let results: any[] = []; - for (const [idx, rawResult] of remoteResults.entries()) { - if (queries[idx]?.method === 'run') { - results.push(rawResult); - continue; - } - - // Drizzle expects each row as an array of its values - const rowValues: unknown[][] = []; - - for (const row of rawResult.rows) { - if (row != null && typeof row === 'object') { - rowValues.push(Object.values(row)); - } - } - - if (queries[idx]?.method === 'get') { - results.push({ rows: rowValues[0] }); - } - - results.push({ rows: rowValues }); - } - return results; - }, - ); - applyTransactionNotSupported(db); - return db; -} - -const errorSchema = z.object({ - success: z.boolean(), - error: z.object({ - code: z.string(), - details: z.string().optional(), - }), -}); - -const KNOWN_ERROR_CODES = { - SQL_QUERY_FAILED: 'SQL_QUERY_FAILED', -}; - -const getUnexpectedResponseMessage = async (response: Response) => - `Unexpected response from remote database:\n(Status ${response.status}) ${await response - .clone() - .text()}`; - -async function parseRemoteError(response: Response): Promise<DetailedLibsqlError> { - let error; - try { - error = errorSchema.parse(await response.clone().json()).error; - } catch { - return new DetailedLibsqlError({ - message: await getUnexpectedResponseMessage(response), - code: KNOWN_ERROR_CODES.SQL_QUERY_FAILED, - }); - } - // Strip LibSQL error prefixes - let baseDetails = - error.details?.replace(/.*SQLite error: /, '') ?? 'Error querying remote database.'; - // Remove duplicated "code" in details - const details = baseDetails.slice(baseDetails.indexOf(':') + 1).trim(); - let hint = `See the Astro DB guide for query and push instructions: https://docs.astro.build/en/guides/astro-db/#query-your-database`; - if (error.code === KNOWN_ERROR_CODES.SQL_QUERY_FAILED && details.includes('no such table')) { - hint = `Did you run \`astro db push\` to push your latest table schemas?`; - } - return new DetailedLibsqlError({ message: details, code: error.code, hint }); -} diff --git a/packages/db/test/basics.test.js b/packages/db/test/basics.test.js index 8d6167447..02f5bce37 100644 --- a/packages/db/test/basics.test.js +++ b/packages/db/test/basics.test.js @@ -181,13 +181,13 @@ describe('astro:db', () => { before(async () => { clearEnvironment(); - process.env.ASTRO_STUDIO_APP_TOKEN = 'some token'; + process.env.ASTRO_DB_APP_TOKEN = 'some token'; remoteDbServer = await setupRemoteDbServer(fixture.config); await fixture.build(); }); after(async () => { - process.env.ASTRO_STUDIO_APP_TOKEN = ''; + process.env.ASTRO_DB_APP_TOKEN = ''; await remoteDbServer?.stop(); }); diff --git a/packages/db/test/ssr-no-apptoken.test.js b/packages/db/test/ssr-no-apptoken.test.js index c570306e5..a78d7bf58 100644 --- a/packages/db/test/ssr-no-apptoken.test.js +++ b/packages/db/test/ssr-no-apptoken.test.js @@ -17,7 +17,7 @@ describe('missing app token', () => { remoteDbServer = await setupRemoteDbServer(fixture.config); await fixture.build(); // Ensure there's no token at runtime - delete process.env.ASTRO_STUDIO_APP_TOKEN; + delete process.env.ASTRO_DB_APP_TOKEN; }); after(async () => { diff --git a/packages/db/test/test-utils.js b/packages/db/test/test-utils.js index b608d75b8..b2a351a47 100644 --- a/packages/db/test/test-utils.js +++ b/packages/db/test/test-utils.js @@ -21,7 +21,7 @@ let portIncrementer = 8030; */ export async function setupRemoteDbServer(astroConfig) { const port = portIncrementer++; - process.env.ASTRO_STUDIO_REMOTE_DB_URL = `http://localhost:${port}`; + process.env.ASTRO_DB_REMOTE_DB_URL = `http://localhost:${port}`; process.env.ASTRO_INTERNAL_TEST_REMOTE = true; const server = createRemoteDbServer().listen(port); @@ -50,7 +50,7 @@ export async function setupRemoteDbServer(astroConfig) { return { server, async stop() { - delete process.env.ASTRO_STUDIO_REMOTE_DB_URL; + delete process.env.ASTRO_DB_REMOTE_DB_URL; delete process.env.ASTRO_INTERNAL_TEST_REMOTE; return new Promise((resolve, reject) => { server.close((err) => { @@ -83,12 +83,12 @@ export async function initializeRemoteDb(astroConfig) { } /** - * Clears the environment variables related to Astro DB and Astro Studio. + * Clears the environment variables related to Astro DB. */ export function clearEnvironment() { const keys = Array.from(Object.keys(process.env)); for (const key of keys) { - if (key.startsWith('ASTRO_DB_') || key.startsWith('ASTRO_STUDIO_')) { + if (key.startsWith('ASTRO_DB_')) { delete process.env[key]; } } diff --git a/packages/db/test/unit/remote-info.test.js b/packages/db/test/unit/remote-info.test.js index 2c58f28b7..c72d59b4f 100644 --- a/packages/db/test/unit/remote-info.test.js +++ b/packages/db/test/unit/remote-info.test.js @@ -8,6 +8,7 @@ describe('RemoteDatabaseInfo', () => { clearEnvironment(); }); + // TODO: what should be the default url for libsql? test('default remote info', () => { const dbInfo = getRemoteDatabaseInfo(); @@ -17,16 +18,6 @@ describe('RemoteDatabaseInfo', () => { }); }); - test('configured Astro Studio remote', () => { - process.env.ASTRO_STUDIO_REMOTE_DB_URL = 'https://studio.astro.build'; - const dbInfo = getRemoteDatabaseInfo(); - - assert.deepEqual(dbInfo, { - type: 'studio', - url: 'https://studio.astro.build', - }); - }); - test('configured libSQL remote', () => { process.env.ASTRO_DB_REMOTE_URL = 'libsql://libsql.self.hosted'; const dbInfo = getRemoteDatabaseInfo(); @@ -36,24 +27,12 @@ describe('RemoteDatabaseInfo', () => { url: 'libsql://libsql.self.hosted', }); }); - - test('configured both libSQL and Studio remote', () => { - process.env.ASTRO_DB_REMOTE_URL = 'libsql://libsql.self.hosted'; - process.env.ASTRO_STUDIO_REMOTE_DB_URL = 'https://studio.astro.build'; - const dbInfo = getRemoteDatabaseInfo(); - - assert.deepEqual(dbInfo, { - type: 'studio', - url: 'https://studio.astro.build', - }); - }); }); describe('RemoteManagedToken', () => { // Avoid conflicts with other tests beforeEach(() => { clearEnvironment(); - process.env.ASTRO_STUDIO_APP_TOKEN = 'studio token'; process.env.ASTRO_DB_APP_TOKEN = 'db token'; }); after(() => { @@ -68,20 +47,7 @@ describe('RemoteManagedToken', () => { test('token for default remote', async () => { const { token } = await getManagedRemoteToken(); - assert.equal(token, 'studio token'); - }); - - test('given token for configured Astro Studio remote', async () => { - process.env.ASTRO_STUDIO_REMOTE_DB_URL = 'https://studio.astro.build'; - const { token } = await getManagedRemoteToken('given token'); - assert.equal(token, 'given token'); - }); - - test('token for configured Astro Studio remote', async () => { - process.env.ASTRO_STUDIO_REMOTE_DB_URL = 'https://studio.astro.build'; - const { token } = await getManagedRemoteToken(); - - assert.equal(token, 'studio token'); + assert.equal(token, 'db token'); }); test('given token for configured libSQL remote', async () => { @@ -97,18 +63,8 @@ describe('RemoteManagedToken', () => { assert.equal(token, 'db token'); }); - test('token for given Astro Studio remote', async () => { - process.env.ASTRO_DB_REMOTE_URL = 'libsql://libsql.self.hosted'; - const { token } = await getManagedRemoteToken(undefined, { - type: 'studio', - url: 'https://studio.astro.build', - }); - - assert.equal(token, 'studio token'); - }); - test('token for given libSQL remote', async () => { - process.env.ASTRO_STUDIO_REMOTE_URL = 'libsql://libsql.self.hosted'; + process.env.ASTRO_DB_REMOTE_URL = 'libsql://libsql.self.hosted'; const { token } = await getManagedRemoteToken(undefined, { type: 'libsql', url: 'libsql://libsql.self.hosted', diff --git a/packages/integrations/cloudflare/src/entrypoints/server.ts b/packages/integrations/cloudflare/src/entrypoints/server.ts index a37f820ab..a236d4336 100644 --- a/packages/integrations/cloudflare/src/entrypoints/server.ts +++ b/packages/integrations/cloudflare/src/entrypoints/server.ts @@ -14,7 +14,6 @@ setGetEnv(createGetEnv(globalEnv as Env)); type Env = { [key: string]: unknown; ASSETS: { fetch: (req: Request | string) => Promise<Response> }; - ASTRO_STUDIO_APP_TOKEN?: string; }; export interface Runtime<T extends object = object> { @@ -73,12 +72,6 @@ export function createExports(manifest: SSRManifest) { request.headers.get('cf-connecting-ip'), ); - process.env.ASTRO_STUDIO_APP_TOKEN ??= (() => { - if (typeof env.ASTRO_STUDIO_APP_TOKEN === 'string') { - return env.ASTRO_STUDIO_APP_TOKEN; - } - })(); - const locals: Runtime = { runtime: { env: env, diff --git a/packages/studio/CHANGELOG.md b/packages/studio/CHANGELOG.md deleted file mode 100644 index d9876af2b..000000000 --- a/packages/studio/CHANGELOG.md +++ /dev/null @@ -1,69 +0,0 @@ -# @astrojs/studio - -## 0.1.9 - -### Patch Changes - -- [#13731](https://github.com/withastro/astro/pull/13731) [`c3e80c2`](https://github.com/withastro/astro/commit/c3e80c25b90c803e2798b752583a8e77cdad3146) Thanks [@jsparkdev](https://github.com/jsparkdev)! - update vite to latest version for fixing CVE - -## 0.1.8 - -### Patch Changes - -- [#13591](https://github.com/withastro/astro/pull/13591) [`5dd2d3f`](https://github.com/withastro/astro/commit/5dd2d3fde8a138ed611dedf39ffa5dfeeed315f8) Thanks [@florian-lefebvre](https://github.com/florian-lefebvre)! - Removes unused code - -## 0.1.7 - -### Patch Changes - -- [#13596](https://github.com/withastro/astro/pull/13596) [`3752519`](https://github.com/withastro/astro/commit/375251966d1b28a570bff45ff0fe7e7d2fe46f72) Thanks [@jsparkdev](https://github.com/jsparkdev)! - update vite to latest version to fix CVE - -- [#13547](https://github.com/withastro/astro/pull/13547) [`360cb91`](https://github.com/withastro/astro/commit/360cb9199a4314f90825c5639ff4396760e9cfcc) Thanks [@jsparkdev](https://github.com/jsparkdev)! - Updates vite to the latest version - -## 0.1.6 - -### Patch Changes - -- [#13526](https://github.com/withastro/astro/pull/13526) [`ff9d69e`](https://github.com/withastro/astro/commit/ff9d69e3443c80059c54f6296d19f66bb068ead3) Thanks [@jsparkdev](https://github.com/jsparkdev)! - update `vite` to the latest version - -## 0.1.5 - -### Patch Changes - -- [#13505](https://github.com/withastro/astro/pull/13505) [`a98ae5b`](https://github.com/withastro/astro/commit/a98ae5b8f5c33900379012e9e253a755c0a8927e) Thanks [@ematipico](https://github.com/ematipico)! - Updates the dependency `vite` to the latest. - -## 0.1.4 - -### Patch Changes - -- [#13011](https://github.com/withastro/astro/pull/13011) [`cf30880`](https://github.com/withastro/astro/commit/cf3088060d45227dcb48e041c4ed5e0081d71398) Thanks [@ascorbic](https://github.com/ascorbic)! - Upgrades Vite - -## 0.1.3 - -### Patch Changes - -- [#12799](https://github.com/withastro/astro/pull/12799) [`739dbfb`](https://github.com/withastro/astro/commit/739dbfba4214107cf8fc40c702834dad33eed3b0) Thanks [@ascorbic](https://github.com/ascorbic)! - Upgrades Vite to pin esbuild - -## 0.1.2 - -### Patch Changes - -- [#12073](https://github.com/withastro/astro/pull/12073) [`acf264d`](https://github.com/withastro/astro/commit/acf264d8c003718cda5a0b9ce5fb7ac1cd6641b6) Thanks [@bluwy](https://github.com/bluwy)! - Replaces `ora` with `yocto-spinner` - -## 0.1.2-beta.0 - -### Patch Changes - -- [#12073](https://github.com/withastro/astro/pull/12073) [`acf264d`](https://github.com/withastro/astro/commit/acf264d8c003718cda5a0b9ce5fb7ac1cd6641b6) Thanks [@bluwy](https://github.com/bluwy)! - Replaces `ora` with `yocto-spinner` - -## 0.1.1 - -### Patch Changes - -- [#11331](https://github.com/withastro/astro/pull/11331) [`f1b78a4`](https://github.com/withastro/astro/commit/f1b78a496034d53b0e9dfc276a4a1b1d691772c4) Thanks [@bluwy](https://github.com/bluwy)! - Relaxes exports condition to allow importing ESM from CJS - -## 0.1.0 - -### Minor Changes - -- [#11037](https://github.com/withastro/astro/pull/11037) [`9332bb1`](https://github.com/withastro/astro/commit/9332bb1c1f237f5666ded09532ccd651837b94e5) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Created package. This package contain shared code between integrations that interact with Astro Studio and is not intended to be used by end-users at this time diff --git a/packages/studio/README.md b/packages/studio/README.md deleted file mode 100644 index 935c200a3..000000000 --- a/packages/studio/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# @astrojs/studio - -This package manages the connection between a local Astro project and [Astro Studio](studio). At this time, this package is not intended for direct use by end users, but rather as a dependency of other Astro packages. - -## Support - -- Get help in the [Astro Discord][discord]. Post questions in our `#support` forum, or visit our dedicated `#dev` channel to discuss current development and more! - -- Check our [Astro Integration Documentation][astro-integration] for more on integrations. - -- Submit bug reports and feature requests as [GitHub issues][issues]. - -## Contributing - -This package is maintained by Astro's Core team. You're welcome to submit an issue or PR! These links will help you get started: - -- [Contributor Manual][contributing] -- [Code of Conduct][coc] -- [Community Guide][community] - -## License - -MIT - -Copyright (c) 2023–present [Astro][astro] - -[astro]: https://astro.build/ -[contributing]: https://github.com/withastro/astro/blob/main/CONTRIBUTING.md -[coc]: https://github.com/withastro/.github/blob/main/CODE_OF_CONDUCT.md -[community]: https://github.com/withastro/.github/blob/main/COMMUNITY_GUIDE.md -[discord]: https://astro.build/chat/ -[issues]: https://github.com/withastro/astro/issues -[astro-integration]: https://docs.astro.build/en/guides/integrations-guide/ -[studio]: https://studio.astro.build/ diff --git a/packages/studio/package.json b/packages/studio/package.json deleted file mode 100644 index 646bb77b1..000000000 --- a/packages/studio/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "@astrojs/studio", - "version": "0.1.9", - "description": "Internal package powering integrations between Astro projects and Astro Studio", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/withastro/astro.git", - "directory": "packages/studio" - }, - "bugs": "https://github.com/withastro/astro/issues", - "homepage": "https://docs.astro.build/en/guides/integrations-guide/studio/", - "type": "module", - "author": "withastro", - "types": "./dist/index.js", - "main": "./dist/index.js", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - }, - "./package.json": "./package.json" - }, - "files": [ - "dist" - ], - "keywords": [ - "withastro", - "astro-integration" - ], - "scripts": { - "build": "astro-scripts build \"src/**/*.ts\" && tsc", - "build:ci": "astro-scripts build \"src/**/*.ts\"", - "dev": "astro-scripts dev \"src/**/*.ts\"" - }, - "dependencies": { - "ci-info": "^4.2.0", - "kleur": "^4.1.5", - "yocto-spinner": "^0.2.1" - }, - "devDependencies": { - "astro-scripts": "workspace:*", - "typescript": "^5.8.3", - "vite": "^6.3.4" - } -} diff --git a/packages/studio/src/core/errors.ts b/packages/studio/src/core/errors.ts deleted file mode 100644 index 9ffead3ee..000000000 --- a/packages/studio/src/core/errors.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { cyan, red } from 'kleur/colors'; - -export const MISSING_SESSION_ID_CI_ERROR = `${red('▶ ASTRO_STUDIO_APP_TOKEN required')} - - To authenticate with Astro Studio add the token to your CI's environment variables.\n`; - -export const MISSING_SESSION_ID_ERROR = `${red('▶ Login required!')} - - To authenticate with Astro Studio, run - ${cyan('astro login')}\n`; - -export const MISSING_PROJECT_ID_ERROR = `${red('▶ Directory not linked.')} - - To link this directory to an Astro Studio project, run - ${cyan('astro link')}\n`; diff --git a/packages/studio/src/core/tokens.ts b/packages/studio/src/core/tokens.ts deleted file mode 100644 index cb43d70ff..000000000 --- a/packages/studio/src/core/tokens.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { readFile } from 'node:fs/promises'; -import { homedir } from 'node:os'; -import { join } from 'node:path'; -import { pathToFileURL } from 'node:url'; -import ci from 'ci-info'; -import { green } from 'kleur/colors'; -import yoctoSpinner from 'yocto-spinner'; -import { - MISSING_PROJECT_ID_ERROR, - MISSING_SESSION_ID_CI_ERROR, - MISSING_SESSION_ID_ERROR, -} from './errors.js'; -import { getAstroStudioEnv, getAstroStudioUrl } from './utils.js'; - -export const SESSION_LOGIN_FILE = pathToFileURL(join(homedir(), '.astro', 'session-token')); -export const PROJECT_ID_FILE = pathToFileURL(join(process.cwd(), '.astro', 'link')); - -export interface ManagedAppToken { - token: string; - renew(): Promise<void>; - destroy(): Promise<void>; -} - -class ManagedLocalAppToken implements ManagedAppToken { - token: string; - constructor(token: string) { - this.token = token; - } - async renew() {} - async destroy() {} -} - -class ManagedRemoteAppToken implements ManagedAppToken { - token: string; - session: string; - projectId: string; - ttl: number; - expires: Date; - renewTimer: NodeJS.Timeout | undefined; - - static async create(sessionToken: string, projectId: string) { - const { token: shortLivedAppToken, ttl } = await this.createToken(sessionToken, projectId); - return new ManagedRemoteAppToken({ - token: shortLivedAppToken, - session: sessionToken, - projectId, - ttl, - }); - } - - static async createToken( - sessionToken: string, - projectId: string, - ): Promise<{ token: string; ttl: number }> { - const spinner = yoctoSpinner({ text: 'Connecting to remote database...' }).start(); - const response = await safeFetch( - new URL(`${getAstroStudioUrl()}/auth/cli/token-create`), - { - method: 'POST', - headers: new Headers({ - Authorization: `Bearer ${sessionToken}`, - }), - body: JSON.stringify({ projectId }), - }, - (res) => { - throw new Error(`Failed to create token: ${res.status} ${res.statusText}`); - }, - ); - spinner.success(green('Connected to remote database.')); - - const { token, ttl } = await response.json(); - return { token, ttl }; - } - - constructor(options: { token: string; session: string; projectId: string; ttl: number }) { - this.token = options.token; - this.session = options.session; - this.projectId = options.projectId; - this.ttl = options.ttl; - this.renewTimer = setTimeout(() => this.renew(), (1000 * 60 * 5) / 2); - this.expires = getExpiresFromTtl(this.ttl); - } - - private async fetch(url: string, body: Record<string, unknown>) { - return safeFetch( - `${getAstroStudioUrl()}${url}`, - { - method: 'POST', - headers: { - Authorization: `Bearer ${this.session}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(body), - }, - () => { - throw new Error(`Failed to fetch ${url}.`); - }, - ); - } - - tokenIsValid() { - return new Date() > this.expires; - } - - createRenewTimer() { - return setTimeout(() => this.renew(), (1000 * 60 * this.ttl) / 2); - } - - async renew() { - clearTimeout(this.renewTimer); - delete this.renewTimer; - - if (this.tokenIsValid()) { - const response = await this.fetch('/auth/cli/token-renew', { - token: this.token, - projectId: this.projectId, - }); - if (response.status === 200) { - this.expires = getExpiresFromTtl(this.ttl); - this.renewTimer = this.createRenewTimer(); - } else { - throw new Error(`Unexpected response: ${response.status} ${response.statusText}`); - } - } else { - try { - const { token, ttl } = await ManagedRemoteAppToken.createToken( - this.session, - this.projectId, - ); - this.token = token; - this.ttl = ttl; - this.expires = getExpiresFromTtl(ttl); - this.renewTimer = this.createRenewTimer(); - } catch { - // If we get here we couldn't create a new token. Since the existing token - // is expired we really can't do anything and should exit. - throw new Error( - `Token has expired and attempts to renew it have failed, please try again.`, - ); - } - } - } - - async destroy() { - try { - const response = await this.fetch('/auth/cli/token-delete', { - token: this.token, - projectId: this.projectId, - }); - if (response.status !== 200) { - throw new Error(`Unexpected response: ${response.status} ${response.statusText}`); - } - } catch (error: any) { - console.error('Failed to delete token.', error?.message); - } - } -} - -export async function getProjectIdFromFile() { - try { - return await readFile(PROJECT_ID_FILE, 'utf-8'); - } catch { - return undefined; - } -} - -export async function getSessionIdFromFile() { - try { - return await readFile(SESSION_LOGIN_FILE, 'utf-8'); - } catch { - return undefined; - } -} - -export async function getManagedAppTokenOrExit(token?: string): Promise<ManagedAppToken> { - if (token) { - return new ManagedLocalAppToken(token); - } - if (process.env.ASTRO_INTERNAL_TEST_REMOTE) { - return new ManagedLocalAppToken('fake' /* token ignored in test */); - } - const { ASTRO_STUDIO_APP_TOKEN } = getAstroStudioEnv(); - if (ASTRO_STUDIO_APP_TOKEN) { - return new ManagedLocalAppToken(ASTRO_STUDIO_APP_TOKEN); - } - const sessionToken = await getSessionIdFromFile(); - if (!sessionToken) { - if (ci.isCI) { - console.error(MISSING_SESSION_ID_CI_ERROR); - } else { - console.error(MISSING_SESSION_ID_ERROR); - } - process.exit(1); - } - const projectId = await getProjectIdFromFile(); - if (!projectId) { - console.error(MISSING_PROJECT_ID_ERROR); - process.exit(1); - } - return ManagedRemoteAppToken.create(sessionToken, projectId); -} - -function getExpiresFromTtl(ttl: number): Date { - // ttl is in minutes - return new Date(Date.now() + ttl * 60 * 1000); -} - -/** - * Small wrapper around fetch that throws an error if the response is not OK. Allows for custom error handling as well through the onNotOK callback. - */ -async function safeFetch( - url: Parameters<typeof fetch>[0], - options: Parameters<typeof fetch>[1] = {}, - onNotOK: (response: Response) => void | Promise<void> = () => { - throw new Error(`Request to ${url} returned a non-OK status code.`); - }, -): Promise<Response> { - const response = await fetch(url, options); - - if (!response.ok) { - await onNotOK(response); - } - - return response; -} diff --git a/packages/studio/src/core/utils.ts b/packages/studio/src/core/utils.ts deleted file mode 100644 index 7cf40f751..000000000 --- a/packages/studio/src/core/utils.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { loadEnv } from 'vite'; - -export function getAstroStudioEnv(envMode = ''): Record<`ASTRO_STUDIO_${string}`, string> { - const env = loadEnv(envMode, process.cwd(), 'ASTRO_STUDIO_'); - return env; -} - -export function getAstroStudioUrl(): string { - const env = getAstroStudioEnv(); - return env.ASTRO_STUDIO_URL || 'https://studio.astro.build'; -} diff --git a/packages/studio/src/index.ts b/packages/studio/src/index.ts deleted file mode 100644 index e97d53dfc..000000000 --- a/packages/studio/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './core/tokens.js'; -export * from './core/utils.js'; -export * from './core/errors.js'; diff --git a/packages/studio/tsconfig.json b/packages/studio/tsconfig.json deleted file mode 100644 index 18443cddf..000000000 --- a/packages/studio/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "include": ["src"], - "compilerOptions": { - "outDir": "./dist" - } -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2a0fabafe..2d54f0da2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4371,9 +4371,6 @@ importers: packages/db: dependencies: - '@astrojs/studio': - specifier: workspace:* - version: link:../studio '@libsql/client': specifier: ^0.15.2 version: 0.15.2 @@ -6198,28 +6195,6 @@ importers: specifier: ^2.0.1 version: 2.0.1 - packages/studio: - dependencies: - ci-info: - specifier: ^4.2.0 - version: 4.2.0 - kleur: - specifier: ^4.1.5 - version: 4.1.5 - yocto-spinner: - specifier: ^0.2.1 - version: 0.2.1 - devDependencies: - astro-scripts: - specifier: workspace:* - version: link:../../scripts - typescript: - specifier: ^5.8.3 - version: 5.8.3 - vite: - specifier: ^6.3.4 - version: 6.3.5(@types/node@22.13.1)(jiti@2.4.2)(lightningcss@1.29.2)(sass@1.86.3)(yaml@2.5.1) - packages/telemetry: dependencies: ci-info: @@ -10694,6 +10669,7 @@ packages: libsql@0.5.4: resolution: {integrity: sha512-GEFeWca4SDAQFxjHWJBE6GK52LEtSskiujbG3rqmmeTO9t4sfSBKIURNLLpKDDF7fb7jmTuuRkDAn9BZGITQNw==} + cpu: [x64, arm64, wasm32] os: [darwin, linux, win32] lightningcss-darwin-arm64@1.29.2: |