mirror of
https://github.com/withastro/astro.git
synced 2025-01-13 22:11:20 -05:00
add short-lived db tokens
This commit is contained in:
parent
92d7d84e6b
commit
eb7989dfda
11 changed files with 360 additions and 29 deletions
|
@ -59,17 +59,19 @@
|
|||
"drizzle-orm": "^0.28.6",
|
||||
"kleur": "^4.1.5",
|
||||
"nanoid": "^5.0.1",
|
||||
"open": "^10.0.3",
|
||||
"ora": "^7.0.1",
|
||||
"prompts": "^2.4.2",
|
||||
"yargs-parser": "^21.1.1",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.3.6",
|
||||
"@types/deep-diff": "^1.0.5",
|
||||
"@types/diff": "^5.0.8",
|
||||
"@types/yargs-parser": "^21.0.3",
|
||||
"@types/chai": "^4.3.6",
|
||||
"@types/mocha": "^10.0.2",
|
||||
"@types/prompts": "^2.4.8",
|
||||
"@types/yargs-parser": "^21.0.3",
|
||||
"astro": "workspace:*",
|
||||
"astro-scripts": "workspace:*",
|
||||
"chai": "^4.3.10",
|
||||
|
|
61
packages/db/src/core/cli/commands/link/index.ts
Normal file
61
packages/db/src/core/cli/commands/link/index.ts
Normal file
|
@ -0,0 +1,61 @@
|
|||
import type { AstroConfig } from 'astro';
|
||||
import { mkdir, writeFile } from 'node:fs/promises';
|
||||
import prompts from 'prompts';
|
||||
import type { Arguments } from 'yargs-parser';
|
||||
import { PROJECT_ID_FILE, getSessionIdFromFile } from '../../../tokens.js';
|
||||
import { getAstroStudioUrl } from '../../../utils.js';
|
||||
|
||||
export async function cmd({ flags }: { config: AstroConfig; flags: Arguments }) {
|
||||
const linkUrl = new URL(getAstroStudioUrl() + '/auth/cli/link');
|
||||
const sessionToken = await getSessionIdFromFile();
|
||||
if (!sessionToken) {
|
||||
console.error('You must be logged in to link a project.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const workspaceIdName = await promptWorkspaceName();
|
||||
const projectIdName = await promptProjectName();
|
||||
|
||||
const response = await fetch(linkUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${await getSessionIdFromFile()}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({projectIdName, workspaceIdName})
|
||||
});
|
||||
if (!response.ok) {
|
||||
console.error(`Failed to link project: ${response.status} ${response.statusText}`);
|
||||
process.exit(1);
|
||||
}
|
||||
const { data } = await response.json();
|
||||
await mkdir(new URL('.', PROJECT_ID_FILE), { recursive: true });
|
||||
await writeFile(PROJECT_ID_FILE, `${data.id}`);
|
||||
console.info('Project linked.');
|
||||
}
|
||||
|
||||
export async function promptProjectName(defaultName?: string): Promise<string> {
|
||||
const { projectName } = await prompts({
|
||||
type: 'text',
|
||||
name: 'projectName',
|
||||
message: 'Project ID',
|
||||
initial: defaultName,
|
||||
});
|
||||
if (typeof projectName !== 'string') {
|
||||
process.exit(0);
|
||||
}
|
||||
return projectName;
|
||||
}
|
||||
|
||||
export async function promptWorkspaceName(defaultName?: string): Promise<string> {
|
||||
const { workspaceName } = await prompts({
|
||||
type: 'text',
|
||||
name: 'workspaceName',
|
||||
message: 'Workspace ID',
|
||||
initial: defaultName,
|
||||
});
|
||||
if (typeof workspaceName !== 'string') {
|
||||
process.exit(0);
|
||||
}
|
||||
return workspaceName;
|
||||
}
|
53
packages/db/src/core/cli/commands/login/index.ts
Normal file
53
packages/db/src/core/cli/commands/login/index.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
import type { AstroConfig } from 'astro';
|
||||
import { cyan } from 'kleur/colors';
|
||||
import { mkdir, writeFile } from 'node:fs/promises';
|
||||
import { createServer } from 'node:http';
|
||||
import ora from 'ora';
|
||||
import type { Arguments } from 'yargs-parser';
|
||||
import { getAstroStudioUrl } from '../../../utils.js';
|
||||
import open from 'open';
|
||||
import { SESSION_LOGIN_FILE } from '../../../tokens.js';
|
||||
|
||||
function serveAndResolveSession(): Promise<string> {
|
||||
let resolve: (value: string | PromiseLike<string>) => void,
|
||||
reject: (value?: string | PromiseLike<string>) => void;
|
||||
const sessionPromise = new Promise<string>((_resolve, _reject) => {
|
||||
resolve = _resolve;
|
||||
reject = _reject;
|
||||
});
|
||||
|
||||
const server = createServer((req, res) => {
|
||||
res.writeHead(200);
|
||||
res.end();
|
||||
const url = new URL(req.url ?? '/', `http://${req.headers.host}`);
|
||||
const session = url.searchParams.get('session');
|
||||
if (!session) {
|
||||
reject();
|
||||
} else {
|
||||
resolve(session);
|
||||
}
|
||||
}).listen(5710, 'localhost');
|
||||
|
||||
return sessionPromise.finally(() => {
|
||||
server.closeAllConnections();
|
||||
server.close();
|
||||
});
|
||||
}
|
||||
|
||||
export async function cmd({ flags }: { config: AstroConfig; flags: Arguments }) {
|
||||
let session = flags.session;
|
||||
const loginUrl = getAstroStudioUrl() + '/auth/cli';
|
||||
|
||||
if (!session) {
|
||||
console.log(`Opening ${cyan(loginUrl)} in your browser...`);
|
||||
console.log(`If something goes wrong, copy-and-paste the URL into your browser.`);
|
||||
open(loginUrl);
|
||||
const spinner = ora('Waiting for confirmation...');
|
||||
session = await serveAndResolveSession();
|
||||
spinner.succeed('Successfully logged in!');
|
||||
}
|
||||
|
||||
await mkdir(new URL('.', SESSION_LOGIN_FILE), { recursive: true });
|
||||
await writeFile(SESSION_LOGIN_FILE, `${session}`);
|
||||
console.log('Logged in 🚀');
|
||||
}
|
9
packages/db/src/core/cli/commands/logout/index.ts
Normal file
9
packages/db/src/core/cli/commands/logout/index.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import type { AstroConfig } from 'astro';
|
||||
import { unlink } from 'node:fs/promises';
|
||||
import type { Arguments } from 'yargs-parser';
|
||||
import { SESSION_LOGIN_FILE } from '../../../tokens.js';
|
||||
|
||||
export async function cmd({ }: { config: AstroConfig; flags: Arguments }) {
|
||||
await unlink(SESSION_LOGIN_FILE);
|
||||
console.log('Successfully logged out of Astro Studio.');
|
||||
}
|
|
@ -2,9 +2,15 @@ import { createClient, type InStatement } from '@libsql/client';
|
|||
import type { AstroConfig } from 'astro';
|
||||
import deepDiff from 'deep-diff';
|
||||
import { drizzle } from 'drizzle-orm/sqlite-proxy';
|
||||
import { red } from 'kleur/colors';
|
||||
import prompts from 'prompts';
|
||||
import type { Arguments } from 'yargs-parser';
|
||||
import type { AstroConfigWithDB } from '../../../types.js';
|
||||
import { APP_TOKEN_ERROR } from '../../../errors.js';
|
||||
import { setupDbTables } from '../../../queries.js';
|
||||
import { getManagedAppToken } from '../../../tokens.js';
|
||||
import type { AstroConfigWithDB, DBSnapshot } from '../../../types.js';
|
||||
import { getRemoteDatabaseUrl } from '../../../utils.js';
|
||||
import { getMigrationQueries } from '../../migration-queries.js';
|
||||
import {
|
||||
createCurrentSnapshot,
|
||||
createEmptySnapshot,
|
||||
|
@ -13,19 +19,12 @@ import {
|
|||
loadInitialSnapshot,
|
||||
loadMigration,
|
||||
} from '../../migrations.js';
|
||||
import type { DBSnapshot } from '../../../types.js';
|
||||
import { getAstroStudioEnv, getRemoteDatabaseUrl } from '../../../utils.js';
|
||||
import { getMigrationQueries } from '../../migration-queries.js';
|
||||
import { setupDbTables } from '../../../queries.js';
|
||||
import prompts from 'prompts';
|
||||
import { red } from 'kleur/colors';
|
||||
|
||||
const { diff } = deepDiff;
|
||||
|
||||
export async function cmd({ config, flags }: { config: AstroConfig; flags: Arguments }) {
|
||||
const isSeedData = flags.seed;
|
||||
const isDryRun = flags.dryRun;
|
||||
const appToken = flags.token ?? getAstroStudioEnv().ASTRO_STUDIO_APP_TOKEN;
|
||||
const currentSnapshot = createCurrentSnapshot(config);
|
||||
const allMigrationFiles = await getMigrations();
|
||||
if (allMigrationFiles.length === 0) {
|
||||
|
@ -40,15 +39,18 @@ export async function cmd({ config, flags }: { config: AstroConfig; flags: Argum
|
|||
console.log(calculatedDiff);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const appToken = await getManagedAppToken(flags.token);
|
||||
if (!appToken) {
|
||||
console.error(APP_TOKEN_ERROR);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// get all migrations from the filesystem
|
||||
const allLocalMigrations = await getMigrations();
|
||||
const { data: missingMigrations } = await prepareMigrateQuery({
|
||||
migrations: allLocalMigrations,
|
||||
appToken,
|
||||
appToken: appToken.token,
|
||||
});
|
||||
// exit early if there are no migrations to push
|
||||
if (missingMigrations.length === 0) {
|
||||
|
@ -58,13 +60,19 @@ export async function cmd({ config, flags }: { config: AstroConfig; flags: Argum
|
|||
// push the database schema
|
||||
if (missingMigrations.length > 0) {
|
||||
console.log(`Pushing ${missingMigrations.length} migrations...`);
|
||||
await pushSchema({ migrations: missingMigrations, appToken, isDryRun, currentSnapshot });
|
||||
await pushSchema({
|
||||
migrations: missingMigrations,
|
||||
appToken: appToken.token,
|
||||
isDryRun,
|
||||
currentSnapshot,
|
||||
});
|
||||
}
|
||||
// push the database seed data
|
||||
if (isSeedData) {
|
||||
console.info('Pushing data...');
|
||||
await pushData({ config, appToken, isDryRun });
|
||||
await pushData({ config, appToken: appToken.token, isDryRun });
|
||||
}
|
||||
await appToken.destroy();
|
||||
console.info('Push complete!');
|
||||
}
|
||||
|
||||
|
@ -201,7 +209,7 @@ async function runMigrateQuery({
|
|||
return new Response(null, { status: 200 });
|
||||
}
|
||||
|
||||
const url = new URL('/db/migrate/run', getRemoteDatabaseUrl());
|
||||
const url = new URL('/migrations/run', getRemoteDatabaseUrl());
|
||||
|
||||
return await fetch(url, {
|
||||
method: 'POST',
|
||||
|
@ -219,7 +227,7 @@ async function prepareMigrateQuery({
|
|||
migrations: string[];
|
||||
appToken: string;
|
||||
}) {
|
||||
const url = new URL('/db/migrate/prepare', getRemoteDatabaseUrl());
|
||||
const url = new URL('/migrations/prepare', getRemoteDatabaseUrl());
|
||||
const requestBody = {
|
||||
migrations,
|
||||
experimentalVersion: 1,
|
||||
|
|
|
@ -1,20 +1,21 @@
|
|||
import type { AstroConfig } from 'astro';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import type { Arguments } from 'yargs-parser';
|
||||
import { APP_TOKEN_ERROR } from '../../../errors.js';
|
||||
import { getAstroStudioEnv, getRemoteDatabaseUrl } from '../../../utils.js';
|
||||
import { createRemoteDatabaseClient } from '../../../../runtime/db-client.js';
|
||||
import { APP_TOKEN_ERROR } from '../../../errors.js';
|
||||
import { getManagedAppToken } from '../../../tokens.js';
|
||||
import { getRemoteDatabaseUrl } from '../../../utils.js';
|
||||
|
||||
export async function cmd({ flags }: { config: AstroConfig; flags: Arguments }) {
|
||||
const query = flags.query;
|
||||
const appToken = flags.token ?? getAstroStudioEnv().ASTRO_STUDIO_APP_TOKEN;
|
||||
const appToken = await getManagedAppToken(flags.token);
|
||||
if (!appToken) {
|
||||
console.error(APP_TOKEN_ERROR);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const db = createRemoteDatabaseClient(appToken, getRemoteDatabaseUrl());
|
||||
const db = createRemoteDatabaseClient(appToken.token, getRemoteDatabaseUrl());
|
||||
// Temporary: create the migration table just in case it doesn't exist
|
||||
const result = await db.run(sql.raw(query));
|
||||
await appToken.destroy();
|
||||
console.log(result);
|
||||
}
|
||||
|
|
|
@ -12,20 +12,32 @@ export async function cli({ flags, config }: { flags: Arguments; config: AstroCo
|
|||
|
||||
switch (command) {
|
||||
case 'shell': {
|
||||
const { cmd: shellCommand } = await import('./commands/shell/index.js');
|
||||
return await shellCommand({ config, flags });
|
||||
const { cmd } = await import('./commands/shell/index.js');
|
||||
return await cmd({ config, flags });
|
||||
}
|
||||
case 'sync': {
|
||||
const { cmd: syncCommand } = await import('./commands/sync/index.js');
|
||||
return await syncCommand({ config, flags });
|
||||
const { cmd } = await import('./commands/sync/index.js');
|
||||
return await cmd({ config, flags });
|
||||
}
|
||||
case 'push': {
|
||||
const { cmd: pushCommand } = await import('./commands/push/index.js');
|
||||
return await pushCommand({ config, flags });
|
||||
const { cmd } = await import('./commands/push/index.js');
|
||||
return await cmd({ config, flags });
|
||||
}
|
||||
case 'verify': {
|
||||
const { cmd: verifyCommand } = await import('./commands/verify/index.js');
|
||||
return await verifyCommand({ config, flags });
|
||||
const { cmd } = await import('./commands/verify/index.js');
|
||||
return await cmd({ config, flags });
|
||||
}
|
||||
case 'login': {
|
||||
const { cmd } = await import('./commands/login/index.js');
|
||||
return await cmd({ config, flags });
|
||||
}
|
||||
case 'logout': {
|
||||
const { cmd } = await import('./commands/logout/index.js');
|
||||
return await cmd({ config, flags });
|
||||
}
|
||||
case 'link': {
|
||||
const { cmd } = await import('./commands/link/index.js');
|
||||
return await cmd({ config, flags });
|
||||
}
|
||||
default: {
|
||||
if (command == null) {
|
||||
|
|
134
packages/db/src/core/tokens.ts
Normal file
134
packages/db/src/core/tokens.ts
Normal file
|
@ -0,0 +1,134 @@
|
|||
import { readFile } from 'node:fs/promises';
|
||||
import { homedir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
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;
|
||||
renewTimer: NodeJS.Timeout | undefined;
|
||||
|
||||
static async create(sessionToken: string, projectId: string) {
|
||||
const response = await fetch(new URL(`${getAstroStudioUrl()}/auth/cli/token-create`), {
|
||||
method: 'POST',
|
||||
headers: new Headers({
|
||||
Authorization: `Bearer ${sessionToken}`,
|
||||
}),
|
||||
body: JSON.stringify({ projectId }),
|
||||
});
|
||||
const { token: shortLivedAppToken, ttl } = (await response.json());
|
||||
return new ManagedRemoteAppToken({
|
||||
token: shortLivedAppToken,
|
||||
session: sessionToken,
|
||||
projectId,
|
||||
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);
|
||||
}
|
||||
|
||||
private async fetch(url: string, body: unknown) {
|
||||
return fetch(`${getAstroStudioUrl()}${url}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.session}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}
|
||||
|
||||
async renew() {
|
||||
clearTimeout(this.renewTimer);
|
||||
delete this.renewTimer;
|
||||
try {
|
||||
const response = await this.fetch('/auth/cli/token-renew', {
|
||||
token: this.token,
|
||||
projectId: this.projectId,
|
||||
});
|
||||
if (response.status === 200) {
|
||||
this.renewTimer = setTimeout(() => this.renew(), (1000 * 60 * this.ttl) / 2);
|
||||
} else {
|
||||
throw new Error(`Unexpected response: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
const retryIn = (60 * this.ttl) / 10;
|
||||
console.error(`Failed to renew token. Retrying in ${retryIn} seconds.`, error?.message);
|
||||
this.renewTimer = setTimeout(() => this.renew(), retryIn * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
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 (error) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSessionIdFromFile() {
|
||||
try {
|
||||
return await readFile(SESSION_LOGIN_FILE, 'utf-8');
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getManagedAppToken(token?: string): Promise<ManagedAppToken | undefined> {
|
||||
if (token) {
|
||||
return new ManagedLocalAppToken(token);
|
||||
}
|
||||
const { ASTRO_STUDIO_APP_TOKEN } = getAstroStudioEnv();
|
||||
if (ASTRO_STUDIO_APP_TOKEN) {
|
||||
return new ManagedLocalAppToken(ASTRO_STUDIO_APP_TOKEN);
|
||||
}
|
||||
const sessionToken = await getSessionIdFromFile();
|
||||
const projectId = await getProjectIdFromFile();
|
||||
if (!sessionToken || !projectId) {
|
||||
return undefined;
|
||||
}
|
||||
return ManagedRemoteAppToken.create(sessionToken, projectId);
|
||||
}
|
|
@ -12,3 +12,8 @@ export function getRemoteDatabaseUrl(): string {
|
|||
const env = getAstroStudioEnv();
|
||||
return env.ASTRO_STUDIO_REMOTE_DB_URL;
|
||||
}
|
||||
|
||||
export function getAstroStudioUrl(): string {
|
||||
const env = getAstroStudioEnv();
|
||||
return env.ASTRO_STUDIO_URL;
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ function checkIfModificationIsAllowed(collections: DBCollections, Table: SQLiteT
|
|||
}
|
||||
|
||||
export function createRemoteDatabaseClient(appToken: string, remoteDbURL: string) {
|
||||
const url = new URL('./db/query/', remoteDbURL);
|
||||
const url = new URL('/db/query', remoteDbURL);
|
||||
|
||||
const db = drizzleProxy(async (sql, parameters, method) => {
|
||||
const requestBody: InStatement = { sql, args: parameters };
|
||||
|
|
46
pnpm-lock.yaml
generated
46
pnpm-lock.yaml
generated
|
@ -3805,6 +3805,12 @@ importers:
|
|||
nanoid:
|
||||
specifier: ^5.0.1
|
||||
version: 5.0.1
|
||||
open:
|
||||
specifier: ^10.0.3
|
||||
version: 10.0.3
|
||||
ora:
|
||||
specifier: ^7.0.1
|
||||
version: 7.0.1
|
||||
prompts:
|
||||
specifier: ^2.4.2
|
||||
version: 2.4.2
|
||||
|
@ -9108,6 +9114,13 @@ packages:
|
|||
ieee754: 1.2.1
|
||||
dev: false
|
||||
|
||||
/bundle-name@4.1.0:
|
||||
resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
|
||||
engines: {node: '>=18'}
|
||||
dependencies:
|
||||
run-applescript: 7.0.0
|
||||
dev: false
|
||||
|
||||
/bytes@3.1.2:
|
||||
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
@ -9799,6 +9812,19 @@ packages:
|
|||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/default-browser-id@5.0.0:
|
||||
resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==}
|
||||
engines: {node: '>=18'}
|
||||
dev: false
|
||||
|
||||
/default-browser@5.2.1:
|
||||
resolution: {integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==}
|
||||
engines: {node: '>=18'}
|
||||
dependencies:
|
||||
bundle-name: 4.1.0
|
||||
default-browser-id: 5.0.0
|
||||
dev: false
|
||||
|
||||
/defaults@1.0.4:
|
||||
resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==}
|
||||
dependencies:
|
||||
|
@ -9814,6 +9840,11 @@ packages:
|
|||
has-property-descriptors: 1.0.1
|
||||
dev: true
|
||||
|
||||
/define-lazy-prop@3.0.0:
|
||||
resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/define-properties@1.2.1:
|
||||
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -13401,6 +13432,16 @@ packages:
|
|||
resolution: {integrity: sha512-6mp/gpLQz/ZwrGLz+i7tSJ3eWNLE1KxLXHO+b6xxRyZ1Alp4TgTcvHiQ89rC2IkvsU3/IRhpIJuxl7rRCwUzLA==}
|
||||
dev: false
|
||||
|
||||
/open@10.0.3:
|
||||
resolution: {integrity: sha512-dtbI5oW7987hwC9qjJTyABldTaa19SuyJse1QboWv3b0qCcrrLNVDqBx1XgELAjh9QTVQaP/C5b1nhQebd1H2A==}
|
||||
engines: {node: '>=18'}
|
||||
dependencies:
|
||||
default-browser: 5.2.1
|
||||
define-lazy-prop: 3.0.0
|
||||
is-inside-container: 1.0.0
|
||||
is-wsl: 3.1.0
|
||||
dev: false
|
||||
|
||||
/optionator@0.9.3:
|
||||
resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
@ -14815,6 +14856,11 @@ packages:
|
|||
resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==}
|
||||
dev: true
|
||||
|
||||
/run-applescript@7.0.0:
|
||||
resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==}
|
||||
engines: {node: '>=18'}
|
||||
dev: false
|
||||
|
||||
/run-parallel@1.2.0:
|
||||
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
||||
dependencies:
|
||||
|
|
Loading…
Add table
Reference in a new issue