diff --git a/.changeset/wide-mirrors-invent.md b/.changeset/wide-mirrors-invent.md
new file mode 100644
index 000000000..c7104d0ab
--- /dev/null
+++ b/.changeset/wide-mirrors-invent.md
@@ -0,0 +1,6 @@
+---
+'@generaltranslation/python-extractor': minor
+'gt': minor
+---
+
+feat: add python support for registration
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 0ca5f26d4..7863420ab 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -117,6 +117,7 @@
"esbuild": "^0.27.2",
"fast-glob": "^3.3.3",
"fast-json-stable-stringify": "^2.1.0",
+ "@generaltranslation/python-extractor": "workspace:*",
"generaltranslation": "workspace:*",
"gt-remark": "workspace:*",
"html-entities": "^2.6.0",
@@ -132,6 +133,7 @@
"remark-parse": "^11.0.0",
"remark-stringify": "^11.0.0",
"resolve": "^1.22.10",
+ "smol-toml": "^1.3.1",
"tsconfig-paths": "^4.2.0",
"unified": "^11.0.5",
"unist-util-visit": "^5.0.0",
diff --git a/packages/cli/src/cli/base.ts b/packages/cli/src/cli/base.ts
index 27d265726..d0dab6507 100644
--- a/packages/cli/src/cli/base.ts
+++ b/packages/cli/src/cli/base.ts
@@ -66,7 +66,7 @@ import {
getAgentInstructions,
appendAgentInstructions,
} from '../setup/agentInstructions.js';
-import { determineLibrary } from '../fs/determineFramework.js';
+import { determineLibrary } from '../fs/determineFramework/index.js';
import { INLINE_LIBRARIES } from '../types/libraries.js';
import { handleEnqueue } from './commands/enqueue.js';
diff --git a/packages/cli/src/cli/inline.ts b/packages/cli/src/cli/inline.ts
index ccbc68185..47d773d69 100644
--- a/packages/cli/src/cli/inline.ts
+++ b/packages/cli/src/cli/inline.ts
@@ -193,10 +193,14 @@ function fallbackToGtReact(library: SupportedLibraries): InlineLibrary {
Libraries.GT_NEXT,
Libraries.GT_NODE,
Libraries.GT_REACT_NATIVE,
+ Libraries.GT_FLASK,
+ Libraries.GT_FASTAPI,
].includes(library as Libraries)
? (library as
| typeof Libraries.GT_NEXT
| typeof Libraries.GT_NODE
- | typeof Libraries.GT_REACT_NATIVE)
+ | typeof Libraries.GT_REACT_NATIVE
+ | typeof Libraries.GT_FLASK
+ | typeof Libraries.GT_FASTAPI)
: Libraries.GT_REACT;
}
diff --git a/packages/cli/src/cli/python.ts b/packages/cli/src/cli/python.ts
new file mode 100644
index 000000000..3f33e2a1f
--- /dev/null
+++ b/packages/cli/src/cli/python.ts
@@ -0,0 +1,17 @@
+import { Command } from 'commander';
+import { SupportedLibraries } from '../types/index.js';
+import { InlineCLI } from './inline.js';
+import { PythonLibrary } from '../types/libraries.js';
+
+/**
+ * CLI tool for managing translations with gt-flask and gt-fastapi
+ */
+export class PythonCLI extends InlineCLI {
+ constructor(
+ command: Command,
+ library: PythonLibrary,
+ additionalModules?: SupportedLibraries[]
+ ) {
+ super(command, library, additionalModules);
+ }
+}
diff --git a/packages/cli/src/config/generateSettings.ts b/packages/cli/src/config/generateSettings.ts
index 2ceb1f0ca..d7955b0da 100644
--- a/packages/cli/src/config/generateSettings.ts
+++ b/packages/cli/src/config/generateSettings.ts
@@ -32,6 +32,17 @@ export const DEFAULT_SRC_PATTERNS = [
'components/**/*.{js,jsx,ts,tsx}',
];
+export const DEFAULT_PYTHON_SRC_PATTERNS = ['**/*.py'];
+export const DEFAULT_PYTHON_SRC_EXCLUDES = [
+ 'venv/**',
+ '.venv/**',
+ '__pycache__/**',
+ '**/migrations/**',
+ '**/tests/**',
+ '**/test_*.py',
+ '**/*_test.py',
+];
+
/**
* Generates settings from any
* @param flags - The CLI flags to generate settings from
@@ -158,8 +169,8 @@ export async function generateSettings(
// Add publish if not provided
mergedOptions.publish = (gtConfig.publish || flags.publish) ?? false;
- // Populate src if not provided
- mergedOptions.src = mergedOptions.src || DEFAULT_SRC_PATTERNS;
+ // Don't default src here — each pipeline (JS/Python) has its own defaults.
+ // Only set src if the user explicitly provided it via flags or config.
// Resolve all glob patterns in the files object
const compositePatterns = [
diff --git a/packages/cli/src/extraction/__tests__/mapToUpdates.test.ts b/packages/cli/src/extraction/__tests__/mapToUpdates.test.ts
new file mode 100644
index 000000000..b25f3a757
--- /dev/null
+++ b/packages/cli/src/extraction/__tests__/mapToUpdates.test.ts
@@ -0,0 +1,111 @@
+import { describe, it, expect } from 'vitest';
+import { mapExtractionResultsToUpdates } from '../mapToUpdates.js';
+import type { ExtractionResult } from '@generaltranslation/python-extractor';
+
+describe('mapExtractionResultsToUpdates', () => {
+ it('maps empty results to empty updates', () => {
+ const updates = mapExtractionResultsToUpdates([]);
+ expect(updates).toEqual([]);
+ });
+
+ it('maps single result with all metadata fields', () => {
+ const results: ExtractionResult[] = [
+ {
+ dataFormat: 'ICU',
+ source: 'Hello, {name}!',
+ metadata: {
+ id: 'greeting',
+ context: 'casual',
+ maxChars: 100,
+ filePaths: ['app.py'],
+ staticId: 'static-1',
+ },
+ },
+ ];
+
+ const updates = mapExtractionResultsToUpdates(results);
+
+ expect(updates).toHaveLength(1);
+ expect(updates[0]).toEqual({
+ dataFormat: 'ICU',
+ source: 'Hello, {name}!',
+ metadata: {
+ id: 'greeting',
+ context: 'casual',
+ maxChars: 100,
+ filePaths: ['app.py'],
+ staticId: 'static-1',
+ },
+ });
+ });
+
+ it('passes through dataFormat correctly', () => {
+ const results: ExtractionResult[] = [
+ {
+ dataFormat: 'JSX',
+ source: '
Hello
',
+ metadata: {},
+ },
+ ];
+
+ const updates = mapExtractionResultsToUpdates(results);
+ expect(updates[0].dataFormat).toBe('JSX');
+ });
+
+ it('handles missing optional metadata', () => {
+ const results: ExtractionResult[] = [
+ {
+ dataFormat: 'ICU',
+ source: 'Simple string',
+ metadata: {},
+ },
+ ];
+
+ const updates = mapExtractionResultsToUpdates(results);
+
+ expect(updates).toHaveLength(1);
+ expect(updates[0].metadata).toEqual({});
+ expect(updates[0].metadata.id).toBeUndefined();
+ expect(updates[0].metadata.context).toBeUndefined();
+ expect(updates[0].metadata.maxChars).toBeUndefined();
+ });
+
+ it('preserves filePaths array', () => {
+ const results: ExtractionResult[] = [
+ {
+ dataFormat: 'ICU',
+ source: 'Multi-file string',
+ metadata: {
+ filePaths: ['routes/index.py', 'routes/auth.py'],
+ },
+ },
+ ];
+
+ const updates = mapExtractionResultsToUpdates(results);
+ expect(updates[0].metadata.filePaths).toEqual([
+ 'routes/index.py',
+ 'routes/auth.py',
+ ]);
+ });
+
+ it('maps multiple results', () => {
+ const results: ExtractionResult[] = [
+ {
+ dataFormat: 'ICU',
+ source: 'Hello',
+ metadata: { id: 'hello' },
+ },
+ {
+ dataFormat: 'ICU',
+ source: 'Goodbye',
+ metadata: { id: 'goodbye', context: 'farewell' },
+ },
+ ];
+
+ const updates = mapExtractionResultsToUpdates(results);
+ expect(updates).toHaveLength(2);
+ expect(updates[0].source).toBe('Hello');
+ expect(updates[1].source).toBe('Goodbye');
+ expect(updates[1].metadata.context).toBe('farewell');
+ });
+});
diff --git a/packages/cli/src/extraction/__tests__/postProcess.test.ts b/packages/cli/src/extraction/__tests__/postProcess.test.ts
new file mode 100644
index 000000000..74a8bfac3
--- /dev/null
+++ b/packages/cli/src/extraction/__tests__/postProcess.test.ts
@@ -0,0 +1,125 @@
+import { describe, it, expect } from 'vitest';
+import {
+ calculateHashes,
+ dedupeUpdates,
+ linkStaticUpdates,
+} from '../postProcess.js';
+import type { Updates } from '../../types/index.js';
+
+describe('calculateHashes', () => {
+ it('generates consistent hashes for same input', async () => {
+ const updates1: Updates = [
+ { dataFormat: 'ICU', source: 'hello', metadata: {} },
+ ];
+ const updates2: Updates = [
+ { dataFormat: 'ICU', source: 'hello', metadata: {} },
+ ];
+
+ await calculateHashes(updates1);
+ await calculateHashes(updates2);
+
+ expect(updates1[0].metadata.hash).toBeDefined();
+ expect(updates1[0].metadata.hash).toBe(updates2[0].metadata.hash);
+ });
+
+ it('generates different hashes for different sources', async () => {
+ const updates: Updates = [
+ { dataFormat: 'ICU', source: 'hello', metadata: {} },
+ { dataFormat: 'ICU', source: 'world', metadata: {} },
+ ];
+
+ await calculateHashes(updates);
+
+ expect(updates[0].metadata.hash).not.toBe(updates[1].metadata.hash);
+ });
+});
+
+describe('dedupeUpdates', () => {
+ it('removes duplicates with same hash, merges filePaths', () => {
+ const updates: Updates = [
+ {
+ dataFormat: 'ICU',
+ source: 'hello',
+ metadata: { hash: 'h1', filePaths: ['pathA'] },
+ },
+ {
+ dataFormat: 'ICU',
+ source: 'hello',
+ metadata: { hash: 'h1', filePaths: ['pathB'] },
+ },
+ ];
+
+ dedupeUpdates(updates);
+
+ expect(updates).toHaveLength(1);
+ expect(updates[0].metadata.filePaths).toEqual(['pathA', 'pathB']);
+ });
+
+ it('keeps distinct entries with different hashes', () => {
+ const updates: Updates = [
+ {
+ dataFormat: 'ICU',
+ source: 'hello',
+ metadata: { hash: 'h1', filePaths: ['pathA'] },
+ },
+ {
+ dataFormat: 'ICU',
+ source: 'world',
+ metadata: { hash: 'h2', filePaths: ['pathB'] },
+ },
+ ];
+
+ dedupeUpdates(updates);
+
+ expect(updates).toHaveLength(2);
+ });
+
+ it('handles entries without hashes', () => {
+ const updates: Updates = [
+ { dataFormat: 'ICU', source: 'no-hash', metadata: {} },
+ {
+ dataFormat: 'ICU',
+ source: 'has-hash',
+ metadata: { hash: 'h1', filePaths: ['pathA'] },
+ },
+ ];
+
+ dedupeUpdates(updates);
+
+ expect(updates).toHaveLength(2);
+ });
+});
+
+describe('linkStaticUpdates', () => {
+ it('groups entries by temporary staticId and assigns shared hash', () => {
+ const updates: Updates = [
+ {
+ dataFormat: 'ICU',
+ source: 'variant-a',
+ metadata: { hash: 'ha', staticId: 'temp-static' },
+ },
+ {
+ dataFormat: 'ICU',
+ source: 'variant-b',
+ metadata: { hash: 'hb', staticId: 'temp-static' },
+ },
+ ];
+
+ linkStaticUpdates(updates);
+
+ // Both should now share the same staticId (derived from their hashes)
+ expect(updates[0].metadata.staticId).toBe(updates[1].metadata.staticId);
+ // The staticId should have been replaced (no longer the temporary value)
+ expect(updates[0].metadata.staticId).not.toBe('temp-static');
+ });
+
+ it('does not modify entries without staticId', () => {
+ const updates: Updates = [
+ { dataFormat: 'ICU', source: 'no-static', metadata: { hash: 'h1' } },
+ ];
+
+ linkStaticUpdates(updates);
+
+ expect(updates[0].metadata.staticId).toBeUndefined();
+ });
+});
diff --git a/packages/cli/src/extraction/index.ts b/packages/cli/src/extraction/index.ts
new file mode 100644
index 000000000..4f9d71da6
--- /dev/null
+++ b/packages/cli/src/extraction/index.ts
@@ -0,0 +1,7 @@
+export type { ExtractionResult, ExtractionMetadata } from './types.js';
+export { mapExtractionResultsToUpdates } from './mapToUpdates.js';
+export {
+ calculateHashes,
+ dedupeUpdates,
+ linkStaticUpdates,
+} from './postProcess.js';
diff --git a/packages/cli/src/extraction/mapToUpdates.ts b/packages/cli/src/extraction/mapToUpdates.ts
new file mode 100644
index 000000000..fa9a11f62
--- /dev/null
+++ b/packages/cli/src/extraction/mapToUpdates.ts
@@ -0,0 +1,25 @@
+import type { ExtractionResult } from '@generaltranslation/python-extractor';
+import type { Updates } from '../types/index.js';
+
+/**
+ * Maps ExtractionResult[] to Updates[] format used by the CLI pipeline
+ */
+export function mapExtractionResultsToUpdates(
+ results: ExtractionResult[]
+): Updates {
+ return results.map((result) => ({
+ dataFormat: result.dataFormat,
+ source: result.source,
+ metadata: {
+ ...(result.metadata.id && { id: result.metadata.id }),
+ ...(result.metadata.context && { context: result.metadata.context }),
+ ...(result.metadata.maxChars != null && {
+ maxChars: result.metadata.maxChars,
+ }),
+ ...(result.metadata.filePaths && {
+ filePaths: result.metadata.filePaths,
+ }),
+ ...(result.metadata.staticId && { staticId: result.metadata.staticId }),
+ },
+ }));
+}
diff --git a/packages/cli/src/extraction/postProcess.ts b/packages/cli/src/extraction/postProcess.ts
new file mode 100644
index 000000000..1c0352cc0
--- /dev/null
+++ b/packages/cli/src/extraction/postProcess.ts
@@ -0,0 +1,94 @@
+import { Updates } from '../types/index.js';
+import { hashSource, hashString } from 'generaltranslation/id';
+
+/**
+ * Calculate hashes for all updates in parallel
+ */
+export async function calculateHashes(updates: Updates): Promise {
+ await Promise.all(
+ updates.map(async (update) => {
+ const hash = hashSource({
+ source: update.source,
+ ...(update.metadata.context && { context: update.metadata.context }),
+ ...(update.metadata.id && { id: update.metadata.id }),
+ ...(update.metadata.maxChars != null && {
+ maxChars: update.metadata.maxChars,
+ }),
+ dataFormat: update.dataFormat,
+ });
+ update.metadata.hash = hash;
+ })
+ );
+}
+
+/**
+ * Dedupe entries by hash, merging filePaths
+ */
+export function dedupeUpdates(updates: Updates): void {
+ const mergedByHash = new Map();
+ const noHashUpdates: (typeof updates)[number][] = [];
+
+ for (const update of updates) {
+ const hash = update.metadata.hash;
+ if (!hash) {
+ noHashUpdates.push(update);
+ continue;
+ }
+
+ const existing = mergedByHash.get(hash);
+ if (!existing) {
+ mergedByHash.set(hash, update);
+ continue;
+ }
+
+ const existingPaths = Array.isArray(existing.metadata.filePaths)
+ ? existing.metadata.filePaths.slice()
+ : [];
+ const newPaths = Array.isArray(update.metadata.filePaths)
+ ? update.metadata.filePaths
+ : [];
+
+ for (const p of newPaths) {
+ if (!existingPaths.includes(p)) {
+ existingPaths.push(p);
+ }
+ }
+
+ if (existingPaths.length) {
+ existing.metadata.filePaths = existingPaths;
+ }
+ }
+
+ const mergedUpdates = [...mergedByHash.values(), ...noHashUpdates];
+ updates.splice(0, updates.length, ...mergedUpdates);
+}
+
+/**
+ * Mark static updates as related by attaching a shared id to static content.
+ * Id is calculated as the hash of the static children's combined hashes.
+ */
+export function linkStaticUpdates(updates: Updates): void {
+ const temporaryStaticIdToUpdates = updates.reduce(
+ (acc: Record, update: Updates[number]) => {
+ if (update.metadata.staticId) {
+ if (!acc[update.metadata.staticId]) {
+ acc[update.metadata.staticId] = [];
+ }
+ acc[update.metadata.staticId].push(update);
+ }
+ return acc;
+ },
+ {} as Record
+ );
+
+ Object.values(temporaryStaticIdToUpdates).forEach((staticUpdates) => {
+ const hashes = staticUpdates
+ .map((update) => update.metadata.hash)
+ .sort()
+ .join('-');
+ const sharedStaticId = hashString(hashes);
+ staticUpdates.forEach((update) => {
+ update.metadata.staticId = sharedStaticId;
+ });
+ });
+}
diff --git a/packages/cli/src/extraction/types.ts b/packages/cli/src/extraction/types.ts
new file mode 100644
index 000000000..4ca97ef98
--- /dev/null
+++ b/packages/cli/src/extraction/types.ts
@@ -0,0 +1,4 @@
+export type {
+ ExtractionResult,
+ ExtractionMetadata,
+} from '@generaltranslation/python-extractor';
diff --git a/packages/cli/src/formats/files/__tests__/aggregateFiles.test.ts b/packages/cli/src/formats/files/__tests__/aggregateFiles.test.ts
index 448f4870e..1d740eae9 100644
--- a/packages/cli/src/formats/files/__tests__/aggregateFiles.test.ts
+++ b/packages/cli/src/formats/files/__tests__/aggregateFiles.test.ts
@@ -5,7 +5,7 @@ import { readFile, getRelative } from '../../../fs/findFilepath.js';
import { parseJson } from '../../json/parseJson.js';
import parseYaml from '../../yaml/parseYaml.js';
import sanitizeFileContent from '../../../utils/sanitizeFileContent.js';
-import { determineLibrary } from '../../../fs/determineFramework.js';
+import { determineLibrary } from '../../../fs/determineFramework/index.js';
import { isValidMdx } from '../../../utils/validateMdx.js';
vi.mock('../../../console/logger.js', () => ({
@@ -18,7 +18,7 @@ vi.mock('../../../fs/findFilepath.js');
vi.mock('../../json/parseJson.js');
vi.mock('../../yaml/parseYaml.js');
vi.mock('../../../utils/sanitizeFileContent.js');
-vi.mock('../../../fs/determineFramework.js');
+vi.mock('../../../fs/determineFramework/index.js');
vi.mock('../../../utils/validateMdx.js');
const mockLogWarning = vi.mocked(logger.warn);
diff --git a/packages/cli/src/formats/files/aggregateFiles.ts b/packages/cli/src/formats/files/aggregateFiles.ts
index 048f63f3e..11279dd08 100644
--- a/packages/cli/src/formats/files/aggregateFiles.ts
+++ b/packages/cli/src/formats/files/aggregateFiles.ts
@@ -7,7 +7,7 @@ import { SUPPORTED_FILE_EXTENSIONS } from './supportedFiles.js';
import { parseJson } from '../json/parseJson.js';
import parseYaml from '../yaml/parseYaml.js';
import YAML from 'yaml';
-import { determineLibrary } from '../../fs/determineFramework.js';
+import { determineLibrary } from '../../fs/determineFramework/index.js';
import { hashStringSync } from '../../utils/hash.js';
import { preprocessContent } from './preprocessContent.js';
export const SUPPORTED_DATA_FORMATS = ['JSX', 'ICU', 'I18NEXT'];
diff --git a/packages/cli/src/fs/determineFramework.ts b/packages/cli/src/fs/determineFramework.ts
deleted file mode 100644
index 98b5b3c3c..000000000
--- a/packages/cli/src/fs/determineFramework.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-import chalk from 'chalk';
-import path from 'node:path';
-import fs from 'node:fs';
-import { SupportedLibraries } from '../types/index.js';
-import { logger } from '../console/logger.js';
-import { Libraries } from '../types/libraries.js';
-
-export function determineLibrary(): {
- library: SupportedLibraries;
- additionalModules: SupportedLibraries[];
-} {
- let library: SupportedLibraries = 'base';
- const additionalModules: SupportedLibraries[] = [];
- try {
- // Get the current working directory (where the CLI is being run)
- const cwd = process.cwd();
- const packageJsonPath = path.join(cwd, 'package.json');
-
- // Check if package.json exists
- if (!fs.existsSync(packageJsonPath)) {
- logger.warn(
- chalk.yellow(
- 'No package.json found in the current directory. Run this command from the root of your project.'
- )
- );
- return { library: 'base', additionalModules: [] };
- }
-
- // Read and parse package.json
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
- const dependencies = {
- ...packageJson.dependencies,
- ...packageJson.devDependencies,
- };
-
- // Check for gt-next or gt-react in dependencies
- if (dependencies[Libraries.GT_NEXT]) {
- library = Libraries.GT_NEXT;
- } else if (dependencies[Libraries.GT_REACT]) {
- library = Libraries.GT_REACT;
- } else if (dependencies[Libraries.GT_REACT_NATIVE]) {
- library = Libraries.GT_REACT_NATIVE;
- } else if (dependencies[Libraries.GT_NODE]) {
- library = Libraries.GT_NODE;
- } else if (dependencies['next-intl']) {
- library = 'next-intl';
- } else if (dependencies['i18next']) {
- library = 'i18next';
- }
-
- if (dependencies['i18next-icu']) {
- additionalModules.push('i18next-icu');
- }
-
- // Fallback to base if neither is found
- return { library, additionalModules };
- } catch (error) {
- logger.error('Error determining framework: ' + String(error));
- return { library: 'base', additionalModules: [] };
- }
-}
diff --git a/packages/cli/src/fs/determineFramework/__tests__/determineLibrary.test.ts b/packages/cli/src/fs/determineFramework/__tests__/determineLibrary.test.ts
new file mode 100644
index 000000000..ad7d303f6
--- /dev/null
+++ b/packages/cli/src/fs/determineFramework/__tests__/determineLibrary.test.ts
@@ -0,0 +1,182 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { Libraries } from '../../../types/libraries.js';
+
+vi.mock('node:fs', () => ({
+ default: {
+ existsSync: vi.fn(),
+ readFileSync: vi.fn(),
+ },
+}));
+
+vi.mock('../../../console/logger.js', () => ({
+ logger: {
+ warn: vi.fn(),
+ error: vi.fn(),
+ },
+}));
+
+import fs from 'node:fs';
+import { determineLibrary } from '../index.js';
+
+const mockExistsSync = vi.mocked(fs.existsSync);
+const mockReadFileSync = vi.mocked(fs.readFileSync);
+
+beforeEach(() => {
+ vi.clearAllMocks();
+ vi.spyOn(process, 'cwd').mockReturnValue('/test-project');
+});
+
+describe('determineLibrary', () => {
+ describe('JS detection (regression)', () => {
+ it('detects gt-next from package.json dependencies', () => {
+ mockExistsSync.mockReturnValue(true);
+ mockReadFileSync.mockReturnValue(
+ JSON.stringify({ dependencies: { 'gt-next': '1.0.0' } })
+ );
+
+ const result = determineLibrary();
+ expect(result.library).toBe(Libraries.GT_NEXT);
+ });
+
+ it('detects gt-react from package.json dependencies', () => {
+ mockExistsSync.mockReturnValue(true);
+ mockReadFileSync.mockReturnValue(
+ JSON.stringify({ dependencies: { 'gt-react': '1.0.0' } })
+ );
+
+ const result = determineLibrary();
+ expect(result.library).toBe(Libraries.GT_REACT);
+ });
+
+ it('detects gt-node from package.json dependencies', () => {
+ mockExistsSync.mockReturnValue(true);
+ mockReadFileSync.mockReturnValue(
+ JSON.stringify({ dependencies: { 'gt-node': '1.0.0' } })
+ );
+
+ const result = determineLibrary();
+ expect(result.library).toBe(Libraries.GT_NODE);
+ });
+
+ it("returns 'base' when package.json has no GT dependencies", () => {
+ mockExistsSync.mockImplementation((path) => {
+ if (String(path).endsWith('package.json')) return true;
+ return false;
+ });
+ mockReadFileSync.mockReturnValue(
+ JSON.stringify({ dependencies: { express: '4.0.0' } })
+ );
+
+ const result = determineLibrary();
+ expect(result.library).toBe('base');
+ });
+
+ it('detects i18next-icu as additional module', () => {
+ mockExistsSync.mockReturnValue(true);
+ mockReadFileSync.mockReturnValue(
+ JSON.stringify({
+ dependencies: { 'gt-react': '1.0.0', 'i18next-icu': '2.0.0' },
+ })
+ );
+
+ const result = determineLibrary();
+ expect(result.additionalModules).toContain('i18next-icu');
+ });
+ });
+
+ describe('Python detection (integration)', () => {
+ it('detects gt-flask from pyproject.toml', () => {
+ mockExistsSync.mockImplementation((path) => {
+ if (String(path).endsWith('pyproject.toml')) return true;
+ return false;
+ });
+ mockReadFileSync.mockReturnValue(`[project]
+dependencies = [
+ "gt-flask>=1.0.0",
+ "flask",
+]
+`);
+
+ const result = determineLibrary();
+ expect(result.library).toBe(Libraries.GT_FLASK);
+ });
+
+ it('detects gt-flask from requirements.txt', () => {
+ mockExistsSync.mockImplementation((path) => {
+ if (String(path).endsWith('requirements.txt')) return true;
+ return false;
+ });
+ mockReadFileSync.mockReturnValue('flask\ngt-flask>=1.0.0\nrequests\n');
+
+ const result = determineLibrary();
+ expect(result.library).toBe(Libraries.GT_FLASK);
+ });
+
+ it('detects gt-fastapi from setup.py', () => {
+ mockExistsSync.mockImplementation((path) => {
+ if (String(path).endsWith('setup.py')) return true;
+ return false;
+ });
+ mockReadFileSync.mockReturnValue(`
+from setuptools import setup
+setup(
+ name="myapp",
+ install_requires=["gt-fastapi>=1.0.0", "uvicorn"],
+)
+`);
+
+ const result = determineLibrary();
+ expect(result.library).toBe(Libraries.GT_FASTAPI);
+ });
+
+ it('prefers JS package.json detection over Python', () => {
+ mockExistsSync.mockReturnValue(true);
+ mockReadFileSync.mockImplementation((path) => {
+ if (String(path).endsWith('package.json')) {
+ return JSON.stringify({ dependencies: { 'gt-react': '1.0.0' } });
+ }
+ if (String(path).endsWith('pyproject.toml')) {
+ return '[project]\ndependencies = ["gt-flask"]';
+ }
+ return '';
+ });
+
+ const result = determineLibrary();
+ expect(result.library).toBe(Libraries.GT_REACT);
+ });
+
+ it('falls through pyproject.toml -> requirements.txt -> setup.py', () => {
+ mockExistsSync.mockImplementation((path) => {
+ if (String(path).endsWith('pyproject.toml')) return true;
+ if (String(path).endsWith('requirements.txt')) return true;
+ if (String(path).endsWith('setup.py')) return true;
+ return false;
+ });
+ mockReadFileSync.mockImplementation((path) => {
+ if (String(path).endsWith('pyproject.toml')) {
+ return '[project]\ndependencies = ["flask"]';
+ }
+ if (String(path).endsWith('requirements.txt')) {
+ return 'flask\ngt-flask\n';
+ }
+ return '';
+ });
+
+ const result = determineLibrary();
+ expect(result.library).toBe(Libraries.GT_FLASK);
+ });
+
+ it("returns 'base' when Python dep files exist but contain no GT packages", () => {
+ mockExistsSync.mockImplementation((path) => {
+ if (String(path).endsWith('pyproject.toml')) return true;
+ return false;
+ });
+ mockReadFileSync.mockReturnValue(
+ '[project]\ndependencies = ["flask", "sqlalchemy"]'
+ );
+
+ const result = determineLibrary();
+ expect(result.library).toBe('base');
+ });
+ });
+});
diff --git a/packages/cli/src/fs/determineFramework/__tests__/matchPyprojectDependency.test.ts b/packages/cli/src/fs/determineFramework/__tests__/matchPyprojectDependency.test.ts
new file mode 100644
index 000000000..24eb6de4c
--- /dev/null
+++ b/packages/cli/src/fs/determineFramework/__tests__/matchPyprojectDependency.test.ts
@@ -0,0 +1,285 @@
+import { describe, it, expect } from 'vitest';
+import { Libraries } from '../../../types/libraries.js';
+import { matchPyprojectDependency } from '../matchPyprojectDependency.js';
+
+describe('matchPyprojectDependency', () => {
+ // ---- Basic detection ----
+
+ it('detects gt-flask in [project] dependencies array', () => {
+ const content = `[project]
+name = "myapp"
+dependencies = [
+ "flask>=2.0",
+ "gt-flask>=1.0.0",
+]
+`;
+ expect(matchPyprojectDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+
+ it('detects gt-fastapi in [project] dependencies array', () => {
+ const content = `[project]
+name = "fast-api-1"
+version = "0.1.0"
+description = "Add your description here"
+readme = "README.md"
+requires-python = ">=3.12"
+dependencies = [
+ "fastapi[standard]>=0.135.1",
+ "gt-fastapi",
+]
+
+[tool.uv.sources]
+gt-fastapi = { path = "../../gt-python/packages/gt-fastapi", editable = true }
+`;
+ expect(matchPyprojectDependency(content)).toBe(Libraries.GT_FASTAPI);
+ });
+
+ it('detects gt-fastapi in poetry-style key = "version"', () => {
+ const content = `[tool.poetry.dependencies]
+python = "^3.9"
+gt-fastapi = "^1.0.0"
+fastapi = "^0.100.0"
+`;
+ expect(matchPyprojectDependency(content)).toBe(Libraries.GT_FASTAPI);
+ });
+
+ it('detects gt-flask in optional-dependencies', () => {
+ const content = `[project.optional-dependencies]
+i18n = [
+ "gt-flask>=1.0.0",
+]
+`;
+ expect(matchPyprojectDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+
+ it('detects in poetry dev dependencies', () => {
+ const content = `[tool.poetry.group.dev.dependencies]
+gt-flask = "^1.0.0"
+pytest = "^7.0"
+`;
+ expect(matchPyprojectDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+
+ it('detects in poetry test group dependencies', () => {
+ const content = `[tool.poetry.group.test.dependencies]
+gt-fastapi = "^1.0.0"
+`;
+ expect(matchPyprojectDependency(content)).toBe(Libraries.GT_FASTAPI);
+ });
+
+ // ---- PEP 503 normalization ----
+
+ it('handles underscore variant gt_flask in array', () => {
+ const content = `[project]
+dependencies = [
+ "gt_flask>=1.0.0",
+]
+`;
+ expect(matchPyprojectDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+
+ it('handles underscore variant in poetry key', () => {
+ const content = `[tool.poetry.dependencies]
+gt_fastapi = "^1.0.0"
+`;
+ expect(matchPyprojectDependency(content)).toBe(Libraries.GT_FASTAPI);
+ });
+
+ it('handles period variant gt.flask in array', () => {
+ const content = `[project]
+dependencies = [
+ "gt.flask>=1.0.0",
+]
+`;
+ expect(matchPyprojectDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+
+ it('handles mixed case GT-Flask in array', () => {
+ const content = `[project]
+dependencies = [
+ "GT-Flask>=1.0.0",
+]
+`;
+ expect(matchPyprojectDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+
+ // ---- Extras syntax (the original bug) ----
+
+ it('handles dependency with extras before gt dep in array', () => {
+ const content = `[project]
+dependencies = [
+ "fastapi[standard]>=0.135.1",
+ "gt-fastapi",
+]
+`;
+ expect(matchPyprojectDependency(content)).toBe(Libraries.GT_FASTAPI);
+ });
+
+ it('handles gt dep with extras in array', () => {
+ const content = `[project]
+dependencies = [
+ "gt-flask[redis]>=1.0.0",
+]
+`;
+ expect(matchPyprojectDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+
+ it('handles multiple deps with extras before gt dep', () => {
+ const content = `[project]
+dependencies = [
+ "uvicorn[standard]>=0.20",
+ "sqlalchemy[asyncio]>=2.0",
+ "gt-fastapi>=1.0",
+]
+`;
+ expect(matchPyprojectDependency(content)).toBe(Libraries.GT_FASTAPI);
+ });
+
+ // ---- Array format variations ----
+
+ it('handles inline array on one line', () => {
+ const content = `[project]
+dependencies = ["flask", "gt-flask>=1.0"]
+`;
+ expect(matchPyprojectDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+
+ it('handles empty dependencies array', () => {
+ const content = `[project]
+dependencies = []
+`;
+ expect(matchPyprojectDependency(content)).toBeNull();
+ });
+
+ it('handles compact array without spaces', () => {
+ const content = `[project]
+dependencies=["gt-flask"]
+`;
+ expect(matchPyprojectDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+
+ // ---- Poetry table format variations ----
+
+ it('handles poetry inline table syntax', () => {
+ const content = `[tool.poetry.dependencies]
+gt-flask = {version = "^1.0", optional = true}
+`;
+ expect(matchPyprojectDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+
+ // ---- False positive prevention ----
+
+ it('ignores gt-flask in non-dependency sections', () => {
+ const content = `[project]
+name = "gt-flask-example"
+description = "An app using gt-flask"
+
+[project.urls]
+homepage = "https://example.com/gt-flask"
+
+[build-system]
+requires = ["setuptools"]
+`;
+ expect(matchPyprojectDependency(content)).toBeNull();
+ });
+
+ it('does not match partial package names in array', () => {
+ const content = `[project]
+dependencies = [
+ "not-gt-flask>=1.0.0",
+ "gt-flask-extra>=1.0.0",
+ "gt-flaskify>=1.0.0",
+]
+`;
+ expect(matchPyprojectDependency(content)).toBeNull();
+ });
+
+ it('does not match partial package names in poetry keys', () => {
+ const content = `[tool.poetry.dependencies]
+gt-flask-utils = "^1.0.0"
+not-gt-flask = "^1.0.0"
+`;
+ expect(matchPyprojectDependency(content)).toBeNull();
+ });
+
+ it('does not false-positive on [build-system] requires containing gt-flask', () => {
+ const content = `[build-system]
+requires = ["gt-flask"]
+`;
+ expect(matchPyprojectDependency(content)).toBeNull();
+ });
+
+ it('does not false-positive on [tool.uv.sources] containing gt-fastapi', () => {
+ const content = `[project]
+dependencies = ["flask"]
+
+[tool.uv.sources]
+gt-fastapi = { path = "../../packages/gt-fastapi", editable = true }
+`;
+ expect(matchPyprojectDependency(content)).toBeNull();
+ });
+
+ // ---- Edge cases ----
+
+ it('returns null for empty content', () => {
+ expect(matchPyprojectDependency('')).toBeNull();
+ });
+
+ it('returns null for invalid TOML content', () => {
+ expect(matchPyprojectDependency('{{invalid toml}}')).toBeNull();
+ });
+
+ it('returns null for content with no dependency sections', () => {
+ const content = `[project]
+name = "myapp"
+version = "1.0"
+`;
+ expect(matchPyprojectDependency(content)).toBeNull();
+ });
+
+ it('handles Windows line endings', () => {
+ const content =
+ '[project]\r\ndependencies = [\r\n "gt-flask>=1.0.0",\r\n]\r\n';
+ expect(matchPyprojectDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+
+ // ---- Multiple dependency locations ----
+
+ it('finds gt-flask even if it appears later after non-matching deps', () => {
+ const content = `[project]
+dependencies = [
+ "flask>=2.0",
+ "requests>=2.28",
+ "sqlalchemy>=2.0",
+ "gt-flask>=1.0.0",
+]
+`;
+ expect(matchPyprojectDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+
+ it('finds gt-flask in optional-deps even when main deps exist without it', () => {
+ const content = `[project]
+dependencies = [
+ "flask>=2.0",
+]
+
+[project.optional-dependencies]
+i18n = [
+ "gt-flask>=1.0.0",
+]
+`;
+ expect(matchPyprojectDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+
+ it('checks poetry deps when PEP 621 deps have no match', () => {
+ const content = `[project]
+dependencies = [
+ "flask>=2.0",
+]
+
+[tool.poetry.dependencies]
+gt-fastapi = "^1.0"
+`;
+ expect(matchPyprojectDependency(content)).toBe(Libraries.GT_FASTAPI);
+ });
+});
diff --git a/packages/cli/src/fs/determineFramework/__tests__/matchRequirementsTxtDependency.test.ts b/packages/cli/src/fs/determineFramework/__tests__/matchRequirementsTxtDependency.test.ts
new file mode 100644
index 000000000..6623ec358
--- /dev/null
+++ b/packages/cli/src/fs/determineFramework/__tests__/matchRequirementsTxtDependency.test.ts
@@ -0,0 +1,211 @@
+import { describe, it, expect } from 'vitest';
+import { Libraries } from '../../../types/libraries.js';
+import { matchRequirementsTxtDependency } from '../matchRequirementsTxtDependency.js';
+
+describe('matchRequirementsTxtDependency', () => {
+ // ---- Basic detection ----
+
+ it('detects plain gt-flask', () => {
+ expect(matchRequirementsTxtDependency('gt-flask\n')).toBe(
+ Libraries.GT_FLASK
+ );
+ });
+
+ it('detects gt-fastapi', () => {
+ expect(matchRequirementsTxtDependency('gt-fastapi\n')).toBe(
+ Libraries.GT_FASTAPI
+ );
+ });
+
+ it('detects gt-flask among other packages', () => {
+ const content = 'flask\nuvicorn\ngt-flask\nrequests\n';
+ expect(matchRequirementsTxtDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+
+ // ---- Version specifier formats ----
+
+ it('handles >= version specifier', () => {
+ expect(matchRequirementsTxtDependency('gt-flask>=1.0.0\n')).toBe(
+ Libraries.GT_FLASK
+ );
+ });
+
+ it('handles == exact version', () => {
+ expect(matchRequirementsTxtDependency('gt-flask==1.2.3\n')).toBe(
+ Libraries.GT_FLASK
+ );
+ });
+
+ it('handles ~= compatible version', () => {
+ expect(matchRequirementsTxtDependency('gt-flask~=1.0\n')).toBe(
+ Libraries.GT_FLASK
+ );
+ });
+
+ it('handles != exclusion', () => {
+ expect(matchRequirementsTxtDependency('gt-flask!=1.0.0\n')).toBe(
+ Libraries.GT_FLASK
+ );
+ });
+
+ it('handles < and > specifiers', () => {
+ expect(matchRequirementsTxtDependency('gt-flask>1.0,<2.0\n')).toBe(
+ Libraries.GT_FLASK
+ );
+ });
+
+ // ---- PEP 503 normalization ----
+
+ it('detects gt_flask underscore variant', () => {
+ expect(matchRequirementsTxtDependency('gt_flask\n')).toBe(
+ Libraries.GT_FLASK
+ );
+ });
+
+ it('detects GT-Flask mixed case', () => {
+ expect(matchRequirementsTxtDependency('GT-Flask\n')).toBe(
+ Libraries.GT_FLASK
+ );
+ });
+
+ it('detects gt.flask period variant', () => {
+ expect(matchRequirementsTxtDependency('gt.flask\n')).toBe(
+ Libraries.GT_FLASK
+ );
+ });
+
+ // ---- Extras syntax ----
+
+ it('handles extras syntax [redis]', () => {
+ expect(matchRequirementsTxtDependency('gt-flask[redis]>=1.0\n')).toBe(
+ Libraries.GT_FLASK
+ );
+ });
+
+ it('handles multiple extras', () => {
+ expect(
+ matchRequirementsTxtDependency('gt-flask[redis,celery]>=1.0\n')
+ ).toBe(Libraries.GT_FLASK);
+ });
+
+ // ---- Environment markers ----
+
+ it('handles environment markers with semicolon', () => {
+ expect(
+ matchRequirementsTxtDependency('gt-flask>=1.0; python_version >= "3.8"\n')
+ ).toBe(Libraries.GT_FLASK);
+ });
+
+ // ---- URL-based requirements ----
+
+ it('handles @ URL syntax', () => {
+ expect(
+ matchRequirementsTxtDependency(
+ 'gt-flask @ https://example.com/gt-flask-1.0.tar.gz\n'
+ )
+ ).toBe(Libraries.GT_FLASK);
+ });
+
+ // ---- Comments ----
+
+ it('ignores full-line comments containing gt-flask', () => {
+ expect(
+ matchRequirementsTxtDependency('# gt-flask is not installed\nflask\n')
+ ).toBeNull();
+ });
+
+ it('ignores inline comments after unrelated package name', () => {
+ expect(
+ matchRequirementsTxtDependency('flask # replaces gt-flask\n')
+ ).toBeNull();
+ });
+
+ it('detects gt-flask even with inline comment after it', () => {
+ expect(
+ matchRequirementsTxtDependency('gt-flask>=1.0 # i18n support\n')
+ ).toBe(Libraries.GT_FLASK);
+ });
+
+ // ---- Pip flags / directives ----
+
+ it('ignores -r include directive', () => {
+ expect(
+ matchRequirementsTxtDependency('-r other-requirements.txt\nflask\n')
+ ).toBeNull();
+ });
+
+ it('ignores --index-url directive', () => {
+ expect(
+ matchRequirementsTxtDependency(
+ '--index-url https://pypi.org/simple\nflask\n'
+ )
+ ).toBeNull();
+ });
+
+ it('ignores -e editable install', () => {
+ expect(
+ matchRequirementsTxtDependency('-e git+https://github.com/x/gt-flask\n')
+ ).toBeNull();
+ });
+
+ it('ignores --find-links', () => {
+ expect(
+ matchRequirementsTxtDependency('--find-links /path/to/gt-flask\nflask\n')
+ ).toBeNull();
+ });
+
+ // ---- Partial name prevention ----
+
+ it('does not match prefix: not-gt-flask', () => {
+ expect(matchRequirementsTxtDependency('not-gt-flask>=1.0\n')).toBeNull();
+ });
+
+ it('does not match suffix: gt-flask-extra', () => {
+ expect(matchRequirementsTxtDependency('gt-flask-extra>=1.0\n')).toBeNull();
+ });
+
+ it('does not match gt-flaskify', () => {
+ expect(matchRequirementsTxtDependency('gt-flaskify>=1.0\n')).toBeNull();
+ });
+
+ // ---- Whitespace / formatting ----
+
+ it('handles leading whitespace', () => {
+ expect(matchRequirementsTxtDependency(' gt-flask>=1.0\n')).toBe(
+ Libraries.GT_FLASK
+ );
+ });
+
+ it('handles trailing whitespace', () => {
+ expect(matchRequirementsTxtDependency('gt-flask \n')).toBe(
+ Libraries.GT_FLASK
+ );
+ });
+
+ it('handles Windows line endings', () => {
+ expect(matchRequirementsTxtDependency('gt-flask>=1.0\r\n')).toBe(
+ Libraries.GT_FLASK
+ );
+ });
+
+ it('handles empty lines between packages', () => {
+ const content = 'flask\n\n\ngt-flask\n\nrequests\n';
+ expect(matchRequirementsTxtDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+
+ it('returns null for empty content', () => {
+ expect(matchRequirementsTxtDependency('')).toBeNull();
+ });
+
+ it('returns null for only comments', () => {
+ expect(
+ matchRequirementsTxtDependency('# gt-flask\n# gt-fastapi\n')
+ ).toBeNull();
+ });
+
+ it('returns null for no matching packages', () => {
+ expect(
+ matchRequirementsTxtDependency('flask\nrequests\nuvicorn\n')
+ ).toBeNull();
+ });
+});
diff --git a/packages/cli/src/fs/determineFramework/__tests__/matchSetupPyDependency.test.ts b/packages/cli/src/fs/determineFramework/__tests__/matchSetupPyDependency.test.ts
new file mode 100644
index 000000000..ac93c3575
--- /dev/null
+++ b/packages/cli/src/fs/determineFramework/__tests__/matchSetupPyDependency.test.ts
@@ -0,0 +1,212 @@
+import { describe, it, expect } from 'vitest';
+import { Libraries } from '../../../types/libraries.js';
+import { matchSetupPyDependency } from '../matchSetupPyDependency.js';
+
+describe('matchSetupPyDependency', () => {
+ // ---- Basic detection ----
+
+ it('detects gt-fastapi in install_requires list', () => {
+ const content = `
+from setuptools import setup
+setup(
+ name="myapp",
+ install_requires=["gt-fastapi>=1.0.0", "uvicorn"],
+)
+`;
+ expect(matchSetupPyDependency(content)).toBe(Libraries.GT_FASTAPI);
+ });
+
+ it('detects gt-flask in install_requires list', () => {
+ const content = `
+setup(
+ install_requires=[
+ "flask>=2.0",
+ "gt-flask>=1.0.0",
+ ],
+)
+`;
+ expect(matchSetupPyDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+
+ it('detects gt-flask in extras_require', () => {
+ const content = `
+setup(
+ extras_require={
+ "i18n": ["gt-flask>=1.0.0"],
+ },
+)
+`;
+ expect(matchSetupPyDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+
+ // ---- PEP 503 normalization ----
+
+ it('detects gt_flask underscore variant', () => {
+ const content = `install_requires=["gt_flask"]`;
+ expect(matchSetupPyDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+
+ it('detects GT-Flask mixed case', () => {
+ const content = `install_requires=["GT-Flask>=1.0"]`;
+ expect(matchSetupPyDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+
+ // ---- Quote styles ----
+
+ it('handles single-quoted strings', () => {
+ const content = `install_requires=['gt-flask>=1.0']`;
+ expect(matchSetupPyDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+
+ it('handles mixed quote styles', () => {
+ const content = `install_requires=['flask', "gt-flask>=1.0"]`;
+ expect(matchSetupPyDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+
+ // ---- Version specifiers ----
+
+ it('handles dependency with extras', () => {
+ const content = `install_requires=["gt-flask[redis]>=1.0"]`;
+ expect(matchSetupPyDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+
+ it('handles exact version', () => {
+ const content = `install_requires=["gt-flask==1.2.3"]`;
+ expect(matchSetupPyDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+
+ // ---- False positive prevention ----
+
+ it('does not match name= field containing gt-flask', () => {
+ const content = `
+setup(
+ name="gt-flask",
+ version="1.0.0",
+ install_requires=["flask"],
+)
+`;
+ // "gt-flask" appears in name, NOT in install_requires
+ expect(matchSetupPyDependency(content)).toBeNull();
+ });
+
+ it('does not match description containing gt-flask', () => {
+ const content = `
+setup(
+ name="myapp",
+ description="A wrapper for gt-flask",
+ install_requires=["flask"],
+)
+`;
+ expect(matchSetupPyDependency(content)).toBeNull();
+ });
+
+ it('does not match gt-flask in comments', () => {
+ const content = `
+# This setup uses gt-flask for i18n
+setup(
+ install_requires=["flask"],
+)
+`;
+ expect(matchSetupPyDependency(content)).toBeNull();
+ });
+
+ it('does not match gt-flask in a string outside install_requires', () => {
+ const content = `
+setup(
+ name="myapp",
+ url="https://example.com/gt-flask",
+ install_requires=["flask"],
+)
+`;
+ expect(matchSetupPyDependency(content)).toBeNull();
+ });
+
+ it('does not match unquoted variable names', () => {
+ const content = `
+import gt_flask_utils
+gt_flask_version = "1.0"
+`;
+ expect(matchSetupPyDependency(content)).toBeNull();
+ });
+
+ it('does not match partial quoted names in install_requires', () => {
+ const content = `install_requires=["my-gt-flask-wrapper"]`;
+ expect(matchSetupPyDependency(content)).toBeNull();
+ });
+
+ it('does not match gt-flask-extra in install_requires', () => {
+ const content = `install_requires=["gt-flask-extra>=1.0"]`;
+ expect(matchSetupPyDependency(content)).toBeNull();
+ });
+
+ // ---- Whitespace / formatting ----
+
+ it('handles spaces around equals sign', () => {
+ const content = `install_requires = ["gt-flask>=1.0"]`;
+ expect(matchSetupPyDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+
+ it('handles multiline install_requires', () => {
+ const content = `
+setup(
+ install_requires = [
+ "flask",
+ "gt-flask>=1.0.0",
+ "requests",
+ ],
+)
+`;
+ expect(matchSetupPyDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+
+ // ---- Edge cases ----
+
+ it('returns null for empty content', () => {
+ expect(matchSetupPyDependency('')).toBeNull();
+ });
+
+ it('returns null when no install_requires or extras_require exists', () => {
+ const content = `
+setup(
+ name="myapp",
+ version="1.0",
+)
+`;
+ expect(matchSetupPyDependency(content)).toBeNull();
+ });
+
+ it('returns null when install_requires has no GT packages', () => {
+ const content = `install_requires=["flask", "requests", "sqlalchemy"]`;
+ expect(matchSetupPyDependency(content)).toBeNull();
+ });
+
+ it('handles strings ending with escaped backslash', () => {
+ // "path\\" has an escaped backslash before the closing quote.
+ // The old code sees \ before " and thinks the quote is escaped,
+ // so inString stays true forever and findMatchingBracket returns -1.
+ const content = `install_requires=["path\\\\", "gt-flask>=1.0"]`;
+ expect(matchSetupPyDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+
+ it('handles strings with multiple escaped backslashes before quote', () => {
+ // Four backslashes = two escaped backslashes, closing quote is real
+ const content = `install_requires=["C:\\\\Users\\\\", "gt-flask"]`;
+ expect(matchSetupPyDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+
+ it('handles nested brackets in extras_require', () => {
+ const content = `
+setup(
+ extras_require={
+ "i18n": [
+ "gt-flask>=1.0",
+ ],
+ "dev": [
+ "pytest",
+ ],
+ },
+)
+`;
+ expect(matchSetupPyDependency(content)).toBe(Libraries.GT_FLASK);
+ });
+});
diff --git a/packages/cli/src/fs/determineFramework/detectPythonLibrary.ts b/packages/cli/src/fs/determineFramework/detectPythonLibrary.ts
new file mode 100644
index 000000000..d073e93d1
--- /dev/null
+++ b/packages/cli/src/fs/determineFramework/detectPythonLibrary.ts
@@ -0,0 +1,40 @@
+import path from 'node:path';
+import fs from 'node:fs';
+import { Libraries } from '../../types/libraries.js';
+import { matchPyprojectDependency } from './matchPyprojectDependency.js';
+import { matchRequirementsTxtDependency } from './matchRequirementsTxtDependency.js';
+import { matchSetupPyDependency } from './matchSetupPyDependency.js';
+
+/**
+ * Detect Python GT library from pyproject.toml, requirements.txt, or setup.py.
+ * Checks files in priority order: pyproject.toml → requirements.txt → setup.py.
+ */
+export function detectPythonLibrary(
+ cwd: string
+): typeof Libraries.GT_FLASK | typeof Libraries.GT_FASTAPI | null {
+ // Check pyproject.toml
+ const pyprojectPath = path.join(cwd, 'pyproject.toml');
+ if (fs.existsSync(pyprojectPath)) {
+ const content = fs.readFileSync(pyprojectPath, 'utf8');
+ const result = matchPyprojectDependency(content);
+ if (result) return result;
+ }
+
+ // Check requirements.txt
+ const requirementsPath = path.join(cwd, 'requirements.txt');
+ if (fs.existsSync(requirementsPath)) {
+ const content = fs.readFileSync(requirementsPath, 'utf8');
+ const result = matchRequirementsTxtDependency(content);
+ if (result) return result;
+ }
+
+ // Check setup.py
+ const setupPath = path.join(cwd, 'setup.py');
+ if (fs.existsSync(setupPath)) {
+ const content = fs.readFileSync(setupPath, 'utf8');
+ const result = matchSetupPyDependency(content);
+ if (result) return result;
+ }
+
+ return null;
+}
diff --git a/packages/cli/src/fs/determineFramework/index.ts b/packages/cli/src/fs/determineFramework/index.ts
new file mode 100644
index 000000000..a957d5f53
--- /dev/null
+++ b/packages/cli/src/fs/determineFramework/index.ts
@@ -0,0 +1,69 @@
+import chalk from 'chalk';
+import path from 'node:path';
+import fs from 'node:fs';
+import { SupportedLibraries } from '../../types/index.js';
+import { logger } from '../../console/logger.js';
+import { Libraries } from '../../types/libraries.js';
+import { detectPythonLibrary } from './detectPythonLibrary.js';
+
+export function determineLibrary(): {
+ library: SupportedLibraries;
+ additionalModules: SupportedLibraries[];
+} {
+ let library: SupportedLibraries = 'base';
+ const additionalModules: SupportedLibraries[] = [];
+ try {
+ // Get the current working directory (where the CLI is being run)
+ const cwd = process.cwd();
+ const packageJsonPath = path.join(cwd, 'package.json');
+
+ // Check if package.json exists
+ if (fs.existsSync(packageJsonPath)) {
+ // Read and parse package.json
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
+ const dependencies = {
+ ...packageJson.dependencies,
+ ...packageJson.devDependencies,
+ };
+
+ // Check for gt-next or gt-react in dependencies
+ if (dependencies[Libraries.GT_NEXT]) {
+ library = Libraries.GT_NEXT;
+ } else if (dependencies[Libraries.GT_REACT]) {
+ library = Libraries.GT_REACT;
+ } else if (dependencies[Libraries.GT_REACT_NATIVE]) {
+ library = Libraries.GT_REACT_NATIVE;
+ } else if (dependencies[Libraries.GT_NODE]) {
+ library = Libraries.GT_NODE;
+ } else if (dependencies['next-intl']) {
+ library = 'next-intl';
+ } else if (dependencies['i18next']) {
+ library = 'i18next';
+ }
+
+ if (dependencies['i18next-icu']) {
+ additionalModules.push('i18next-icu');
+ }
+ }
+
+ // If no JS library found, check for Python project files
+ if (library === 'base') {
+ const pythonLibrary = detectPythonLibrary(cwd);
+ if (pythonLibrary) {
+ library = pythonLibrary;
+ } else if (!fs.existsSync(path.join(cwd, 'package.json'))) {
+ logger.warn(
+ chalk.yellow(
+ 'No package.json or Python project file found in the current directory. Run this command from the root of your project.'
+ )
+ );
+ }
+ }
+
+ // Fallback to base if neither is found
+ return { library, additionalModules };
+ } catch (error) {
+ logger.error('Error determining framework: ' + String(error));
+ return { library: 'base', additionalModules: [] };
+ }
+}
diff --git a/packages/cli/src/fs/determineFramework/matchPyprojectDependency.ts b/packages/cli/src/fs/determineFramework/matchPyprojectDependency.ts
new file mode 100644
index 000000000..5a6741657
--- /dev/null
+++ b/packages/cli/src/fs/determineFramework/matchPyprojectDependency.ts
@@ -0,0 +1,98 @@
+import { parse } from 'smol-toml';
+import { Libraries } from '../../types/libraries.js';
+import { resolveGtDependency } from './resolveGtDependency.js';
+
+/**
+ * Parse pyproject.toml for GT dependencies using a proper TOML parser.
+ *
+ * Checks the following locations:
+ * - PEP 621: project.dependencies, project.optional-dependencies.*
+ * - Poetry: tool.poetry.dependencies, tool.poetry.group.*.dependencies
+ */
+export function matchPyprojectDependency(
+ content: string
+): typeof Libraries.GT_FLASK | typeof Libraries.GT_FASTAPI | null {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ let parsed: Record;
+ try {
+ parsed = parse(content) as Record;
+ } catch {
+ return null;
+ }
+
+ // 1. PEP 621: project.dependencies = ["gt-flask>=1.0", ...]
+ const projectDeps = parsed?.project?.dependencies;
+ if (Array.isArray(projectDeps)) {
+ const result = matchDependencyArray(projectDeps);
+ if (result) return result;
+ }
+
+ // 2. PEP 621: project.optional-dependencies.* = ["gt-flask", ...]
+ const optDeps = parsed?.project?.['optional-dependencies'];
+ if (optDeps && typeof optDeps === 'object') {
+ for (const group of Object.values(optDeps)) {
+ if (Array.isArray(group)) {
+ const result = matchDependencyArray(group as string[]);
+ if (result) return result;
+ }
+ }
+ }
+
+ // 3. Poetry: tool.poetry.dependencies = { gt-flask = "^1.0", ... }
+ const poetryDeps = parsed?.tool?.poetry?.dependencies;
+ if (poetryDeps && typeof poetryDeps === 'object') {
+ const result = matchDependencyKeys(poetryDeps);
+ if (result) return result;
+ }
+
+ // 4. Poetry groups: tool.poetry.group.*.dependencies
+ const poetryGroups = parsed?.tool?.poetry?.group;
+ if (poetryGroups && typeof poetryGroups === 'object') {
+ for (const group of Object.values(poetryGroups)) {
+ if (group && typeof group === 'object' && 'dependencies' in group) {
+ const result = matchDependencyKeys(
+ (group as Record).dependencies as Record<
+ string,
+ unknown
+ >
+ );
+ if (result) return result;
+ }
+ }
+ }
+
+ return null;
+}
+
+/**
+ * Check a PEP 508 dependency array (e.g. ["gt-flask>=1.0", "flask[extra]"])
+ * for GT dependencies.
+ */
+function matchDependencyArray(
+ deps: string[]
+): typeof Libraries.GT_FLASK | typeof Libraries.GT_FASTAPI | null {
+ for (const dep of deps) {
+ if (typeof dep !== 'string') continue;
+ // Extract package name before version specifiers, extras, or markers
+ const pkgName = dep.split(/[><=!~;@\s[]/)[0].trim();
+ if (pkgName) {
+ const result = resolveGtDependency(pkgName);
+ if (result) return result;
+ }
+ }
+ return null;
+}
+
+/**
+ * Check Poetry-style dependency keys (e.g. { gt-flask = "^1.0" })
+ * for GT dependencies.
+ */
+function matchDependencyKeys(
+ deps: Record
+): typeof Libraries.GT_FLASK | typeof Libraries.GT_FASTAPI | null {
+ for (const key of Object.keys(deps)) {
+ const result = resolveGtDependency(key);
+ if (result) return result;
+ }
+ return null;
+}
diff --git a/packages/cli/src/fs/determineFramework/matchRequirementsTxtDependency.ts b/packages/cli/src/fs/determineFramework/matchRequirementsTxtDependency.ts
new file mode 100644
index 000000000..170dfddb2
--- /dev/null
+++ b/packages/cli/src/fs/determineFramework/matchRequirementsTxtDependency.ts
@@ -0,0 +1,23 @@
+import { Libraries } from '../../types/libraries.js';
+import { resolveGtDependency } from './resolveGtDependency.js';
+
+/**
+ * Parse requirements.txt for GT dependencies.
+ * Each line is a package specification: package[extras]>=version
+ */
+export function matchRequirementsTxtDependency(
+ content: string
+): typeof Libraries.GT_FLASK | typeof Libraries.GT_FASTAPI | null {
+ const lines = content.split('\n');
+ for (const line of lines) {
+ const trimmed = line.split('#')[0].trim();
+ if (!trimmed || trimmed.startsWith('-')) continue;
+ // Extract package name before version specifiers, extras, or markers
+ const pkgName = trimmed.split(/[><=!~;@\s[]/)[0].trim();
+ if (pkgName) {
+ const result = resolveGtDependency(pkgName);
+ if (result) return result;
+ }
+ }
+ return null;
+}
diff --git a/packages/cli/src/fs/determineFramework/matchSetupPyDependency.ts b/packages/cli/src/fs/determineFramework/matchSetupPyDependency.ts
new file mode 100644
index 000000000..096dece52
--- /dev/null
+++ b/packages/cli/src/fs/determineFramework/matchSetupPyDependency.ts
@@ -0,0 +1,106 @@
+import { Libraries } from '../../types/libraries.js';
+import { resolveGtDependency } from './resolveGtDependency.js';
+
+/**
+ * Parse setup.py for GT dependencies.
+ * Extracts quoted strings from install_requires or extras_require blocks
+ * and matches against GT packages.
+ */
+export function matchSetupPyDependency(
+ content: string
+): typeof Libraries.GT_FLASK | typeof Libraries.GT_FASTAPI | null {
+ // Find install_requires=[...] and extras_require={...:[...]} blocks
+ // and extract quoted dependency strings from within them.
+ // We search for the keyword, then collect quoted strings until the
+ // corresponding closing bracket.
+ const dependencyKeywords = ['install_requires', 'extras_require'];
+
+ for (const keyword of dependencyKeywords) {
+ const keywordIndex = content.indexOf(keyword);
+ if (keywordIndex === -1) continue;
+
+ // Find the opening bracket after the keyword
+ const afterKeyword = content.slice(keywordIndex + keyword.length);
+ const bracketMatch = afterKeyword.match(/\s*=\s*[\[{]/);
+ if (!bracketMatch) continue;
+
+ const openBracket =
+ afterKeyword[bracketMatch.index! + bracketMatch[0].length - 1];
+ const closeBracket = openBracket === '[' ? ']' : '}';
+
+ // Extract the content between brackets
+ const startIndex =
+ keywordIndex +
+ keyword.length +
+ bracketMatch.index! +
+ bracketMatch[0].length;
+ const closeIndex = findMatchingBracket(
+ content,
+ startIndex - 1,
+ openBracket,
+ closeBracket
+ );
+ if (closeIndex === -1) continue;
+
+ const blockContent = content.slice(startIndex, closeIndex);
+
+ // Extract all quoted strings from the block
+ const quotedStrings = blockContent.match(/["']([^"']+)["']/g);
+ if (!quotedStrings) continue;
+
+ for (const match of quotedStrings) {
+ const value = match.slice(1, -1);
+ const pkgName = value.split(/[><=!~;@\s[]/)[0].trim();
+ if (pkgName) {
+ const result = resolveGtDependency(pkgName);
+ if (result) return result;
+ }
+ }
+ }
+
+ return null;
+}
+
+/**
+ * Find the matching closing bracket, handling nesting.
+ */
+function findMatchingBracket(
+ content: string,
+ startIndex: number,
+ openBracket: string,
+ closeBracket: string
+): number {
+ let depth = 0;
+ let inString = false;
+ let stringChar = '';
+
+ for (let i = startIndex; i < content.length; i++) {
+ const ch = content[i];
+
+ // Track string boundaries to avoid counting brackets inside strings
+ if (!inString && (ch === '"' || ch === "'")) {
+ inString = true;
+ stringChar = ch;
+ } else if (inString && ch === stringChar) {
+ // Count consecutive backslashes before this quote
+ let backslashes = 0;
+ let j = i - 1;
+ while (j >= 0 && content[j] === '\\') {
+ backslashes++;
+ j--;
+ }
+ if (backslashes % 2 === 0) inString = false;
+ }
+
+ if (inString) continue;
+
+ if (ch === openBracket) {
+ depth++;
+ } else if (ch === closeBracket) {
+ depth--;
+ if (depth === 0) return i;
+ }
+ }
+
+ return -1;
+}
diff --git a/packages/cli/src/fs/determineFramework/resolveGtDependency.ts b/packages/cli/src/fs/determineFramework/resolveGtDependency.ts
new file mode 100644
index 000000000..1f7ff362f
--- /dev/null
+++ b/packages/cli/src/fs/determineFramework/resolveGtDependency.ts
@@ -0,0 +1,20 @@
+import { Libraries } from '../../types/libraries.js';
+import { PYTHON_GT_DEPENDENCIES } from '@generaltranslation/python-extractor';
+
+/**
+ * Resolve a dependency name (hyphenated or underscored) to a Python GT library.
+ * Per PEP 503, Python package names are normalized: hyphens, underscores, and
+ * periods are interchangeable. We match both gt-flask/gt_flask forms.
+ */
+export function resolveGtDependency(
+ pkgName: string
+): typeof Libraries.GT_FLASK | typeof Libraries.GT_FASTAPI | null {
+ // Normalize: replace underscores/periods with hyphens, lowercase
+ const normalized = pkgName.toLowerCase().replace(/[_.]/g, '-');
+ for (const dep of PYTHON_GT_DEPENDENCIES) {
+ if (normalized === dep) {
+ return dep === 'gt-flask' ? Libraries.GT_FLASK : Libraries.GT_FASTAPI;
+ }
+ }
+ return null;
+}
diff --git a/packages/cli/src/generated/version.ts b/packages/cli/src/generated/version.ts
index 4e2c1bcf3..a763608ef 100644
--- a/packages/cli/src/generated/version.ts
+++ b/packages/cli/src/generated/version.ts
@@ -1,2 +1,2 @@
// This file is auto-generated. Do not edit manually.
-export const PACKAGE_VERSION = '2.7.0';
+export const PACKAGE_VERSION = '2.8.0-alpha.0';
diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts
index 2bc061385..b2d45cdd4 100644
--- a/packages/cli/src/index.ts
+++ b/packages/cli/src/index.ts
@@ -1,10 +1,11 @@
import { BaseCLI } from './cli/base.js';
import { NextCLI } from './cli/next.js';
import { ReactCLI } from './cli/react.js';
-import { determineLibrary } from './fs/determineFramework.js';
+import { PythonCLI } from './cli/python.js';
+import { determineLibrary } from './fs/determineFramework/index.js';
import { Command } from 'commander';
import { NodeCLI } from './cli/node.js';
-import { Libraries } from './types/libraries.js';
+import { Libraries, isPythonLibrary } from './types/libraries.js';
export function main(program: Command) {
program.name('gt');
@@ -20,6 +21,8 @@ export function main(program: Command) {
cli = new ReactCLI(program, library, additionalModules);
} else if (library === Libraries.GT_NODE) {
cli = new NodeCLI(program, library, additionalModules);
+ } else if (isPythonLibrary(library)) {
+ cli = new PythonCLI(program, library, additionalModules);
} else {
cli = new BaseCLI(program, library, additionalModules);
}
diff --git a/packages/cli/src/python/parse/__tests__/createPythonInlineUpdates.test.ts b/packages/cli/src/python/parse/__tests__/createPythonInlineUpdates.test.ts
new file mode 100644
index 000000000..d1f3666db
--- /dev/null
+++ b/packages/cli/src/python/parse/__tests__/createPythonInlineUpdates.test.ts
@@ -0,0 +1,113 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+
+// Mock dependencies before importing the module
+vi.mock('@generaltranslation/python-extractor', () => ({
+ extractFromPythonSource: vi.fn(),
+}));
+
+vi.mock('../../../fs/matchFiles.js', () => ({
+ matchFiles: vi.fn(),
+}));
+
+vi.mock('node:fs', () => ({
+ default: {
+ promises: {
+ readFile: vi.fn(),
+ },
+ },
+}));
+
+import { extractFromPythonSource } from '@generaltranslation/python-extractor';
+import { matchFiles } from '../../../fs/matchFiles.js';
+import fs from 'node:fs';
+import { createPythonInlineUpdates } from '../createPythonInlineUpdates.js';
+
+const mockExtract = vi.mocked(extractFromPythonSource);
+const mockMatchFiles = vi.mocked(matchFiles);
+const mockReadFile = vi.mocked(fs.promises.readFile);
+
+beforeEach(() => {
+ vi.clearAllMocks();
+});
+
+describe('createPythonInlineUpdates', () => {
+ it('returns empty updates when no files match', async () => {
+ mockMatchFiles.mockReturnValue([]);
+
+ const result = await createPythonInlineUpdates(undefined);
+
+ expect(result.updates).toEqual([]);
+ expect(result.errors).toEqual([]);
+ expect(result.warnings).toEqual([]);
+ });
+
+ it('calls extractFromPythonSource for each matched .py file', async () => {
+ mockMatchFiles.mockReturnValue(['/app/routes.py', '/app/models.py']);
+ mockReadFile.mockResolvedValue('# python code' as any);
+ mockExtract.mockReturnValue({
+ results: [],
+ errors: [],
+ warnings: [],
+ });
+
+ await createPythonInlineUpdates(undefined);
+
+ expect(mockExtract).toHaveBeenCalledTimes(2);
+ expect(mockExtract).toHaveBeenCalledWith('# python code', '/app/routes.py');
+ expect(mockExtract).toHaveBeenCalledWith('# python code', '/app/models.py');
+ });
+
+ it('maps results through mapExtractionResultsToUpdates', async () => {
+ mockMatchFiles.mockReturnValue(['/app/routes.py']);
+ mockReadFile.mockResolvedValue('code' as any);
+ mockExtract.mockReturnValue({
+ results: [
+ {
+ dataFormat: 'ICU',
+ source: 'Hello',
+ metadata: { id: 'greeting', filePaths: ['/app/routes.py'] },
+ },
+ ],
+ errors: [],
+ warnings: [],
+ });
+
+ const result = await createPythonInlineUpdates(undefined);
+
+ expect(result.updates).toHaveLength(1);
+ expect(result.updates[0].source).toBe('Hello');
+ expect(result.updates[0].metadata.id).toBe('greeting');
+ expect(result.updates[0].metadata.filePaths).toEqual(['/app/routes.py']);
+ });
+
+ it('propagates errors and warnings from extractor', async () => {
+ mockMatchFiles.mockReturnValue(['/app/routes.py']);
+ mockReadFile.mockResolvedValue('code' as any);
+ mockExtract.mockReturnValue({
+ results: [],
+ errors: ['Parse error in line 5'],
+ warnings: ['Unused import'],
+ });
+
+ const result = await createPythonInlineUpdates(undefined);
+
+ expect(result.errors).toContain('Parse error in line 5');
+ expect(result.warnings).toContain('Unused import');
+ });
+
+ it('handles extraction errors gracefully', async () => {
+ mockMatchFiles.mockReturnValue(['/app/routes.py']);
+ mockReadFile.mockResolvedValue('code' as any);
+ mockExtract.mockImplementation(() => {
+ throw new Error(
+ 'Not implemented: Python extraction is under development'
+ );
+ });
+
+ const result = await createPythonInlineUpdates(undefined);
+
+ expect(result.errors).toHaveLength(1);
+ expect(result.errors[0]).toContain('Error extracting from /app/routes.py');
+ expect(result.updates).toEqual([]);
+ });
+});
diff --git a/packages/cli/src/python/parse/createPythonInlineUpdates.ts b/packages/cli/src/python/parse/createPythonInlineUpdates.ts
new file mode 100644
index 000000000..15ca5d9e1
--- /dev/null
+++ b/packages/cli/src/python/parse/createPythonInlineUpdates.ts
@@ -0,0 +1,53 @@
+import fs from 'node:fs';
+import { Updates } from '../../types/index.js';
+import { extractFromPythonSource } from '@generaltranslation/python-extractor';
+import { mapExtractionResultsToUpdates } from '../../extraction/mapToUpdates.js';
+import {
+ calculateHashes,
+ dedupeUpdates,
+ linkStaticUpdates,
+} from '../../extraction/postProcess.js';
+import { matchFiles } from '../../fs/matchFiles.js';
+import {
+ DEFAULT_PYTHON_SRC_PATTERNS,
+ DEFAULT_PYTHON_SRC_EXCLUDES,
+} from '../../config/generateSettings.js';
+
+export async function createPythonInlineUpdates(
+ filePatterns: string[] | undefined
+): Promise<{ updates: Updates; errors: string[]; warnings: string[] }> {
+ const updates: Updates = [];
+ const errors: string[] = [];
+ const warnings: string[] = [];
+
+ // Match Python source files, excluding common non-source directories
+ const patterns = filePatterns || DEFAULT_PYTHON_SRC_PATTERNS;
+ const files = matchFiles(process.cwd(), [
+ ...patterns,
+ ...DEFAULT_PYTHON_SRC_EXCLUDES.map((p) => `!${p}`),
+ ]);
+
+ for (const file of files) {
+ const code = await fs.promises.readFile(file, 'utf8');
+ try {
+ const {
+ results,
+ errors: fileErrors,
+ warnings: fileWarnings,
+ } = await extractFromPythonSource(code, file);
+
+ updates.push(...mapExtractionResultsToUpdates(results));
+ errors.push(...fileErrors);
+ warnings.push(...fileWarnings);
+ } catch (error) {
+ errors.push(`Error extracting from ${file}: ${String(error)}`);
+ }
+ }
+
+ // Post processing steps
+ await calculateHashes(updates);
+ dedupeUpdates(updates);
+ linkStaticUpdates(updates);
+
+ return { updates, errors, warnings };
+}
diff --git a/packages/cli/src/react/parse/createInlineUpdates.ts b/packages/cli/src/react/parse/createInlineUpdates.ts
index 8a5ac1cc5..d2ef81de7 100644
--- a/packages/cli/src/react/parse/createInlineUpdates.ts
+++ b/packages/cli/src/react/parse/createInlineUpdates.ts
@@ -2,7 +2,6 @@ import fs from 'node:fs';
import { Updates } from '../../types/index.js';
import { parse } from '@babel/parser';
-import { hashSource, hashString } from 'generaltranslation/id';
import { parseTranslationComponent } from '../jsx/utils/jsxParsing/parseJsx.js';
import { parseStrings } from '../jsx/utils/parseStringFunction.js';
import { logger } from '../../console/logger.js';
@@ -16,6 +15,11 @@ import {
REACT_LIBRARIES,
ReactLibrary,
} from '../../types/libraries.js';
+import {
+ calculateHashes,
+ dedupeUpdates,
+ linkStaticUpdates,
+} from '../../extraction/postProcess.js';
export async function createInlineUpdates(
pkg: GTLibrary,
@@ -113,99 +117,4 @@ function getUpstreamPackages(pkg: GTLibrary): GTLibrary[] {
return GT_LIBRARIES_UPSTREAM[pkg];
}
-/**
- * Calculate hashes
- */
-async function calculateHashes(updates: Updates): Promise {
- // parallel calculation of hashes
- await Promise.all(
- updates.map(async (update) => {
- const hash = hashSource({
- source: update.source,
- ...(update.metadata.context && { context: update.metadata.context }),
- ...(update.metadata.id && { id: update.metadata.id }),
- ...(update.metadata.maxChars != null && {
- maxChars: update.metadata.maxChars,
- }),
- dataFormat: update.dataFormat,
- });
- update.metadata.hash = hash;
- })
- );
-}
-
-/**
- * Dedupe entries
- */
-function dedupeUpdates(updates: Updates): void {
- const mergedByHash = new Map();
- const noHashUpdates: (typeof updates)[number][] = [];
-
- for (const update of updates) {
- const hash = update.metadata.hash;
- if (!hash) {
- noHashUpdates.push(update);
- continue;
- }
-
- const existing = mergedByHash.get(hash);
- if (!existing) {
- mergedByHash.set(hash, update);
- continue;
- }
-
- const existingPaths = Array.isArray(existing.metadata.filePaths)
- ? existing.metadata.filePaths.slice()
- : [];
- const newPaths = Array.isArray(update.metadata.filePaths)
- ? update.metadata.filePaths
- : [];
-
- for (const p of newPaths) {
- if (!existingPaths.includes(p)) {
- existingPaths.push(p);
- }
- }
-
- if (existingPaths.length) {
- existing.metadata.filePaths = existingPaths;
- }
- }
-
- const mergedUpdates = [...mergedByHash.values(), ...noHashUpdates];
- updates.splice(0, updates.length, ...mergedUpdates);
-}
-
-/**
- * Mark static updates as the related by attaching a shared id to static content
- * Id is calculated as the hash of the static children's combined hashes
- */
-function linkStaticUpdates(updates: Updates): void {
- // construct map of temporary static ids to updates
- const temporaryStaticIdToUpdates = updates.reduce(
- (acc: Record, update: Updates[number]) => {
- if (update.metadata.staticId) {
- if (!acc[update.metadata.staticId]) {
- acc[update.metadata.staticId] = [];
- }
- acc[update.metadata.staticId].push(update);
- }
- return acc;
- },
- {} as Record
- );
-
- // Calculate shared static ids
- Object.values(temporaryStaticIdToUpdates).forEach((staticUpdates) => {
- const hashes = staticUpdates
- .map((update) => update.metadata.hash)
- .sort()
- .join('-');
- const sharedStaticId = hashString(hashes);
- staticUpdates.forEach((update) => {
- update.metadata.staticId = sharedStaticId;
- });
- });
-}
-
export { dedupeUpdates as _test_dedupeUpdates };
diff --git a/packages/cli/src/translation/parse.ts b/packages/cli/src/translation/parse.ts
index 5dfd4d89c..074c49f96 100644
--- a/packages/cli/src/translation/parse.ts
+++ b/packages/cli/src/translation/parse.ts
@@ -4,11 +4,12 @@ import { logger } from '../console/logger.js';
import loadJSON from '../fs/loadJSON.js';
import { createDictionaryUpdates } from '../react/parse/createDictionaryUpdates.js';
import { createInlineUpdates } from '../react/parse/createInlineUpdates.js';
+import { createPythonInlineUpdates } from '../python/parse/createPythonInlineUpdates.js';
import createESBuildConfig from '../react/config/createESBuildConfig.js';
import chalk from 'chalk';
import type { ParsingConfigOptions } from '../types/parsing.js';
import { exitSync } from '../console/logging.js';
-import { InlineLibrary } from '../types/libraries.js';
+import { InlineLibrary, isPythonLibrary } from '../types/libraries.js';
/**
* Searches for gt-react or gt-next dictionary files and creates updates for them,
@@ -67,12 +68,14 @@ export async function createUpdates(
];
}
}
- // Scan through project for tags
+ // Scan through project for translatable content
const {
updates: newUpdates,
errors: newErrors,
warnings: newWarnings,
- } = await createInlineUpdates(pkg, validate, src, parsingOptions);
+ } = isPythonLibrary(pkg)
+ ? await createPythonInlineUpdates(src)
+ : await createInlineUpdates(pkg, validate, src, parsingOptions);
errors = [...errors, ...newErrors];
warnings = [...warnings, ...newWarnings];
diff --git a/packages/cli/src/types/__tests__/libraries.test.ts b/packages/cli/src/types/__tests__/libraries.test.ts
new file mode 100644
index 000000000..9ca070021
--- /dev/null
+++ b/packages/cli/src/types/__tests__/libraries.test.ts
@@ -0,0 +1,53 @@
+import { describe, it, expect } from 'vitest';
+import {
+ Libraries,
+ isPythonLibrary,
+ INLINE_LIBRARIES,
+ GT_LIBRARIES_UPSTREAM,
+ PYTHON_LIBRARIES,
+} from '../libraries.js';
+
+describe('Python library types', () => {
+ it('isPythonLibrary returns true for gt-flask', () => {
+ expect(isPythonLibrary('gt-flask')).toBe(true);
+ });
+
+ it('isPythonLibrary returns true for gt-fastapi', () => {
+ expect(isPythonLibrary('gt-fastapi')).toBe(true);
+ });
+
+ it('isPythonLibrary returns false for gt-react', () => {
+ expect(isPythonLibrary('gt-react')).toBe(false);
+ });
+
+ it('isPythonLibrary returns false for arbitrary strings', () => {
+ expect(isPythonLibrary('flask')).toBe(false);
+ expect(isPythonLibrary('base')).toBe(false);
+ });
+
+ it('GT_FLASK and GT_FASTAPI are in INLINE_LIBRARIES', () => {
+ expect(
+ (INLINE_LIBRARIES as readonly string[]).includes(Libraries.GT_FLASK)
+ ).toBe(true);
+ expect(
+ (INLINE_LIBRARIES as readonly string[]).includes(Libraries.GT_FASTAPI)
+ ).toBe(true);
+ });
+
+ it('GT_LIBRARIES_UPSTREAM has entries for both Python libraries', () => {
+ expect(GT_LIBRARIES_UPSTREAM[Libraries.GT_FLASK]).toBeDefined();
+ expect(GT_LIBRARIES_UPSTREAM[Libraries.GT_FASTAPI]).toBeDefined();
+ expect(GT_LIBRARIES_UPSTREAM[Libraries.GT_FLASK]).toContain(
+ Libraries.GT_FLASK
+ );
+ expect(GT_LIBRARIES_UPSTREAM[Libraries.GT_FASTAPI]).toContain(
+ Libraries.GT_FASTAPI
+ );
+ });
+
+ it('PYTHON_LIBRARIES contains both Python libraries', () => {
+ expect(PYTHON_LIBRARIES).toContain(Libraries.GT_FLASK);
+ expect(PYTHON_LIBRARIES).toContain(Libraries.GT_FASTAPI);
+ expect(PYTHON_LIBRARIES).toHaveLength(2);
+ });
+});
diff --git a/packages/cli/src/types/index.ts b/packages/cli/src/types/index.ts
index b0b8aa67c..0d146d74d 100644
--- a/packages/cli/src/types/index.ts
+++ b/packages/cli/src/types/index.ts
@@ -208,7 +208,7 @@ export type Settings = {
_versionId?: string; // internal use only
version?: string; // for specifying a custom version id to use. Should be unique
description?: string;
- src: string[]; // list of glob patterns for gt-next and gt-react
+ src?: string[]; // list of glob patterns for source file scanning
framework?: SupportedFrameworks;
options?: AdditionalOptions;
modelProvider?: string;
diff --git a/packages/cli/src/types/libraries.ts b/packages/cli/src/types/libraries.ts
index a8934adc6..41714f313 100644
--- a/packages/cli/src/types/libraries.ts
+++ b/packages/cli/src/types/libraries.ts
@@ -8,6 +8,8 @@ export enum Libraries {
GT_NODE = 'gt-node',
GT_I18N = 'gt-i18n',
GT_REACT_CORE = '@generaltranslation/react-core',
+ GT_FLASK = 'gt-flask',
+ GT_FASTAPI = 'gt-fastapi',
}
/**
@@ -20,6 +22,8 @@ export const GT_LIBRARIES = [
Libraries.GT_NODE,
Libraries.GT_I18N,
Libraries.GT_REACT_CORE,
+ Libraries.GT_FLASK,
+ Libraries.GT_FASTAPI,
] as const;
export type GTLibrary = (typeof GT_LIBRARIES)[number];
@@ -33,6 +37,8 @@ export const INLINE_LIBRARIES = [
Libraries.GT_REACT_NATIVE,
Libraries.GT_REACT_CORE,
Libraries.GT_I18N,
+ Libraries.GT_FLASK,
+ Libraries.GT_FASTAPI,
] as const;
export type InlineLibrary = (typeof INLINE_LIBRARIES)[number];
@@ -51,6 +57,19 @@ export const REACT_LIBRARIES = [
] as const;
export type ReactLibrary = (typeof REACT_LIBRARIES)[number];
+/**
+ * Python libraries
+ */
+export const PYTHON_LIBRARIES = [
+ Libraries.GT_FLASK,
+ Libraries.GT_FASTAPI,
+] as const;
+export type PythonLibrary = (typeof PYTHON_LIBRARIES)[number];
+
+export function isPythonLibrary(lib: string): lib is PythonLibrary {
+ return (PYTHON_LIBRARIES as readonly string[]).includes(lib);
+}
+
/**
* A mapping of each library to their upstream dependencies for filtering imports
*/
@@ -76,4 +95,6 @@ export const GT_LIBRARIES_UPSTREAM: Record = {
[Libraries.GT_NODE]: [Libraries.GT_I18N, Libraries.GT_NODE],
[Libraries.GT_REACT_CORE]: [Libraries.GT_I18N, Libraries.GT_REACT_CORE],
[Libraries.GT_I18N]: [Libraries.GT_I18N],
+ [Libraries.GT_FLASK]: [Libraries.GT_FLASK],
+ [Libraries.GT_FASTAPI]: [Libraries.GT_FASTAPI],
} as const;
diff --git a/packages/python-extractor/package.json b/packages/python-extractor/package.json
new file mode 100644
index 000000000..0b8740a62
--- /dev/null
+++ b/packages/python-extractor/package.json
@@ -0,0 +1,68 @@
+{
+ "name": "@generaltranslation/python-extractor",
+ "version": "0.0.0",
+ "description": "Python source code extraction for General Translation",
+ "main": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "type": "module",
+ "author": "General Translation, Inc.",
+ "license": "FSL-1.1-ALv2",
+ "scripts": {
+ "build": "tsc",
+ "build:watch": "tsc --watch",
+ "build:clean": "sh ../../scripts/clean.sh && pnpm run build",
+ "build:release": "pnpm run build:clean",
+ "format": "prettier --write src",
+ "patch": "pnpm version patch",
+ "release": "pnpm run build:clean && pnpm publish",
+ "release:alpha": "pnpm run build:clean && pnpm publish --tag alpha",
+ "release:beta": "pnpm run build:clean && pnpm publish --tag beta",
+ "release:latest": "pnpm run build:clean && pnpm publish --tag latest",
+ "lint": "eslint \"src/**/*.{js,ts}\"",
+ "lint:fix": "eslint \"src/**/*.{js,ts}\" --fix",
+ "test": "vitest run",
+ "test:watch": "vitest"
+ },
+ "exports": {
+ ".": {
+ "import": "./dist/index.js",
+ "types": "./dist/index.d.ts"
+ },
+ "./types": {
+ "import": "./dist/types.js",
+ "types": "./dist/types.d.ts"
+ },
+ "./constants": {
+ "import": "./dist/constants.js",
+ "types": "./dist/constants.d.ts"
+ }
+ },
+ "files": [
+ "dist"
+ ],
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/generaltranslation/gt.git"
+ },
+ "keywords": [
+ "python",
+ "extraction",
+ "translation",
+ "i18n",
+ "flask",
+ "fastapi"
+ ],
+ "devDependencies": {
+ "@types/node": "^22.5.1",
+ "typescript": "^5.5.4",
+ "vitest": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ },
+ "dependencies": {
+ "generaltranslation": "workspace:*",
+ "tree-sitter-python": "^0.25.0",
+ "web-tree-sitter": "^0.26.6"
+ }
+}
diff --git a/packages/python-extractor/src/__tests__/fixtures/aliased_import.py b/packages/python-extractor/src/__tests__/fixtures/aliased_import.py
new file mode 100644
index 000000000..ad29f374d
--- /dev/null
+++ b/packages/python-extractor/src/__tests__/fixtures/aliased_import.py
@@ -0,0 +1,3 @@
+from gt_flask import t as translate
+
+greeting = translate("Hello world")
diff --git a/packages/python-extractor/src/__tests__/fixtures/declare_cartesian.py b/packages/python-extractor/src/__tests__/fixtures/declare_cartesian.py
new file mode 100644
index 000000000..194af988e
--- /dev/null
+++ b/packages/python-extractor/src/__tests__/fixtures/declare_cartesian.py
@@ -0,0 +1,3 @@
+from gt_flask import t, declare_static
+
+a = t(f"{declare_static('good' if x else 'bad')} {declare_static('day' if y else 'night')}")
diff --git a/packages/python-extractor/src/__tests__/fixtures/declare_complex_aliased.py b/packages/python-extractor/src/__tests__/fixtures/declare_complex_aliased.py
new file mode 100644
index 000000000..c6d835011
--- /dev/null
+++ b/packages/python-extractor/src/__tests__/fixtures/declare_complex_aliased.py
@@ -0,0 +1,13 @@
+from gt_flask import t, declare_static as alias_declare_static, declare_var as alias_declare_var
+
+def get_name():
+ return alias_declare_var('Alice') + '!'
+
+def get_gender(variant):
+ return 'she' if variant == 1 else 'he'
+
+def get_adjective(variant):
+ return 'beautiful' if variant == 1 else alias_declare_var('handsome')
+
+def get_string(variant):
+ return t(f'The {alias_declare_static(get_gender(variant))} is {alias_declare_static(get_adjective(variant))}')
diff --git a/packages/python-extractor/src/__tests__/fixtures/declare_mixed.py b/packages/python-extractor/src/__tests__/fixtures/declare_mixed.py
new file mode 100644
index 000000000..75b3c4f36
--- /dev/null
+++ b/packages/python-extractor/src/__tests__/fixtures/declare_mixed.py
@@ -0,0 +1,3 @@
+from gt_flask import t, declare_static, declare_var
+
+a = t(f"{declare_static('day' if x else 'night')} for {declare_var(name)}")
diff --git a/packages/python-extractor/src/__tests__/fixtures/declare_static_concat.py b/packages/python-extractor/src/__tests__/fixtures/declare_static_concat.py
new file mode 100644
index 000000000..11481f613
--- /dev/null
+++ b/packages/python-extractor/src/__tests__/fixtures/declare_static_concat.py
@@ -0,0 +1,9 @@
+from gt_flask import t, declare_static
+
+def get_time():
+ if is_morning():
+ return "morning"
+ else:
+ return "evening"
+
+a = t("Hello " + declare_static("day" if x else "night") + "!")
diff --git a/packages/python-extractor/src/__tests__/fixtures/declare_static_func.py b/packages/python-extractor/src/__tests__/fixtures/declare_static_func.py
new file mode 100644
index 000000000..66e0dbc50
--- /dev/null
+++ b/packages/python-extractor/src/__tests__/fixtures/declare_static_func.py
@@ -0,0 +1,9 @@
+from gt_flask import t, declare_static
+
+def get_time():
+ if is_morning():
+ return "morning"
+ else:
+ return "evening"
+
+a = t(f"It is {declare_static(get_time())}!")
diff --git a/packages/python-extractor/src/__tests__/fixtures/declare_static_func_simple.py b/packages/python-extractor/src/__tests__/fixtures/declare_static_func_simple.py
new file mode 100644
index 000000000..784c27464
--- /dev/null
+++ b/packages/python-extractor/src/__tests__/fixtures/declare_static_func_simple.py
@@ -0,0 +1,6 @@
+from gt_flask import t, declare_static
+
+def get_name():
+ return '!'
+
+a = t(f"Hello, {declare_static(get_name())}!")
diff --git a/packages/python-extractor/src/__tests__/fixtures/declare_static_helper.py b/packages/python-extractor/src/__tests__/fixtures/declare_static_helper.py
new file mode 100644
index 000000000..5141c5aaa
--- /dev/null
+++ b/packages/python-extractor/src/__tests__/fixtures/declare_static_helper.py
@@ -0,0 +1,5 @@
+def get_time():
+ if is_morning():
+ return "morning"
+ else:
+ return "evening"
diff --git a/packages/python-extractor/src/__tests__/fixtures/declare_static_string_concat.py b/packages/python-extractor/src/__tests__/fixtures/declare_static_string_concat.py
new file mode 100644
index 000000000..6fbbef961
--- /dev/null
+++ b/packages/python-extractor/src/__tests__/fixtures/declare_static_string_concat.py
@@ -0,0 +1,4 @@
+from gt_flask import t, declare_static
+
+# String concatenation directly inside declare_static
+a = t(f"Hello, {declare_static('a' + 'b')}!")
diff --git a/packages/python-extractor/src/__tests__/fixtures/declare_static_ternary.py b/packages/python-extractor/src/__tests__/fixtures/declare_static_ternary.py
new file mode 100644
index 000000000..f4016a3cd
--- /dev/null
+++ b/packages/python-extractor/src/__tests__/fixtures/declare_static_ternary.py
@@ -0,0 +1,10 @@
+from gt_flask import t, declare_static
+
+# Simple ternary
+a = t(f"It is {declare_static('day' if is_day() else 'night')}!")
+
+# Nested ternary (3 branches)
+b = t(f"{declare_static('a' if x else 'b' if y else 'c')}")
+
+# Plain string in declare_static
+c = t(f"Hello {declare_static('world')}!")
diff --git a/packages/python-extractor/src/__tests__/fixtures/declare_var.py b/packages/python-extractor/src/__tests__/fixtures/declare_var.py
new file mode 100644
index 000000000..080c9d5d7
--- /dev/null
+++ b/packages/python-extractor/src/__tests__/fixtures/declare_var.py
@@ -0,0 +1,7 @@
+from gt_flask import t, declare_var
+
+# Basic declare_var
+a = t(f"Hello {declare_var(name)}!")
+
+# declare_var with _name kwarg
+b = t(f"Hello {declare_var(name, _name='user')}!")
diff --git a/packages/python-extractor/src/__tests__/fixtures/declare_var_in_func.py b/packages/python-extractor/src/__tests__/fixtures/declare_var_in_func.py
new file mode 100644
index 000000000..f22a96fbe
--- /dev/null
+++ b/packages/python-extractor/src/__tests__/fixtures/declare_var_in_func.py
@@ -0,0 +1,6 @@
+from gt_flask import t, declare_static, declare_var
+
+def get_name():
+ return declare_var(name) + '!'
+
+a = t(f"Hello, {declare_static(get_name() if True else 'fallback')}!")
diff --git a/packages/python-extractor/src/__tests__/fixtures/declare_var_in_func_only.py b/packages/python-extractor/src/__tests__/fixtures/declare_var_in_func_only.py
new file mode 100644
index 000000000..f52c99091
--- /dev/null
+++ b/packages/python-extractor/src/__tests__/fixtures/declare_var_in_func_only.py
@@ -0,0 +1,6 @@
+from gt_flask import t, declare_static, declare_var
+
+def get_name():
+ return declare_var(name) + '!'
+
+a = t(f"Hello, {declare_static(get_name())}!")
diff --git a/packages/python-extractor/src/__tests__/fixtures/errors.py b/packages/python-extractor/src/__tests__/fixtures/errors.py
new file mode 100644
index 000000000..d095ff8e5
--- /dev/null
+++ b/packages/python-extractor/src/__tests__/fixtures/errors.py
@@ -0,0 +1,6 @@
+from gt_flask import t
+
+# These should produce errors
+a = t(f"Hello {name}")
+b = t(variable_name)
+c = t("Valid string")
diff --git a/packages/python-extractor/src/__tests__/fixtures/fastapi.py b/packages/python-extractor/src/__tests__/fixtures/fastapi.py
new file mode 100644
index 000000000..8a711a750
--- /dev/null
+++ b/packages/python-extractor/src/__tests__/fixtures/fastapi.py
@@ -0,0 +1,3 @@
+from gt_fastapi import t
+
+msg = t("Hello from FastAPI")
diff --git a/packages/python-extractor/src/__tests__/fixtures/kwargs_metadata.py b/packages/python-extractor/src/__tests__/fixtures/kwargs_metadata.py
new file mode 100644
index 000000000..9d8f0f1b6
--- /dev/null
+++ b/packages/python-extractor/src/__tests__/fixtures/kwargs_metadata.py
@@ -0,0 +1,3 @@
+from gt_flask import t
+
+msg = t("Hello", _id="greeting", _context="casual", _max_chars=50)
diff --git a/packages/python-extractor/src/__tests__/fixtures/mixed_imports.py b/packages/python-extractor/src/__tests__/fixtures/mixed_imports.py
new file mode 100644
index 000000000..c1cfb1961
--- /dev/null
+++ b/packages/python-extractor/src/__tests__/fixtures/mixed_imports.py
@@ -0,0 +1,7 @@
+import os
+from gt_flask import t
+from flask import Flask
+
+app = Flask(__name__)
+greeting = t("Hello from Flask")
+path = os.path.join("/tmp", "file.txt")
diff --git a/packages/python-extractor/src/__tests__/fixtures/multi_import.py b/packages/python-extractor/src/__tests__/fixtures/multi_import.py
new file mode 100644
index 000000000..3ee9fc716
--- /dev/null
+++ b/packages/python-extractor/src/__tests__/fixtures/multi_import.py
@@ -0,0 +1,4 @@
+from gt_flask import t, msg
+
+a = t("From t function")
+b = msg("From msg function")
diff --git a/packages/python-extractor/src/__tests__/fixtures/multiple_calls.py b/packages/python-extractor/src/__tests__/fixtures/multiple_calls.py
new file mode 100644
index 000000000..9a02f7a90
--- /dev/null
+++ b/packages/python-extractor/src/__tests__/fixtures/multiple_calls.py
@@ -0,0 +1,6 @@
+from gt_flask import t
+
+a = t("First")
+b = t("Second")
+c = t("Third")
+d = t("Fourth", _id="fourth", _context="test")
diff --git a/packages/python-extractor/src/__tests__/fixtures/no_gt_imports.py b/packages/python-extractor/src/__tests__/fixtures/no_gt_imports.py
new file mode 100644
index 000000000..e32a801f0
--- /dev/null
+++ b/packages/python-extractor/src/__tests__/fixtures/no_gt_imports.py
@@ -0,0 +1,5 @@
+import os
+from flask import Flask
+
+app = Flask(__name__)
+print("Hello world")
diff --git a/packages/python-extractor/src/__tests__/fixtures/reexport_chain_mid.py b/packages/python-extractor/src/__tests__/fixtures/reexport_chain_mid.py
new file mode 100644
index 000000000..8d95aa1c0
--- /dev/null
+++ b/packages/python-extractor/src/__tests__/fixtures/reexport_chain_mid.py
@@ -0,0 +1 @@
+from static_test_defs import get_gender
diff --git a/packages/python-extractor/src/__tests__/fixtures/reexport_chain_top.py b/packages/python-extractor/src/__tests__/fixtures/reexport_chain_top.py
new file mode 100644
index 000000000..c43ae8a1c
--- /dev/null
+++ b/packages/python-extractor/src/__tests__/fixtures/reexport_chain_top.py
@@ -0,0 +1 @@
+from reexport_chain_mid import get_gender
diff --git a/packages/python-extractor/src/__tests__/fixtures/reexport_funcs.py b/packages/python-extractor/src/__tests__/fixtures/reexport_funcs.py
new file mode 100644
index 000000000..a2cb02d20
--- /dev/null
+++ b/packages/python-extractor/src/__tests__/fixtures/reexport_funcs.py
@@ -0,0 +1 @@
+from static_test_defs import get_adjective, get_gender, get_name
diff --git a/packages/python-extractor/src/__tests__/fixtures/simple.py b/packages/python-extractor/src/__tests__/fixtures/simple.py
new file mode 100644
index 000000000..05bb762f7
--- /dev/null
+++ b/packages/python-extractor/src/__tests__/fixtures/simple.py
@@ -0,0 +1,4 @@
+from gt_flask import t
+
+greeting = t("Hello world")
+farewell = t("Goodbye")
diff --git a/packages/python-extractor/src/__tests__/fixtures/static_test_defs.py b/packages/python-extractor/src/__tests__/fixtures/static_test_defs.py
new file mode 100644
index 000000000..5aa163dd7
--- /dev/null
+++ b/packages/python-extractor/src/__tests__/fixtures/static_test_defs.py
@@ -0,0 +1,11 @@
+from gt_fastapi import declare_var as alias_declare_var, declare_static as alias_declare_static
+
+
+def get_name():
+ return alias_declare_var('Alice') + '!'
+
+def get_gender(variant):
+ return 'she' if variant == 1 else 'he'
+
+def get_adjective(variant):
+ return 'beautiful' if variant == 1 else alias_declare_var('handsome')
diff --git a/packages/python-extractor/src/__tests__/index.test.ts b/packages/python-extractor/src/__tests__/index.test.ts
new file mode 100644
index 000000000..d9efabddc
--- /dev/null
+++ b/packages/python-extractor/src/__tests__/index.test.ts
@@ -0,0 +1,860 @@
+import { describe, it, expect } from 'vitest';
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import {
+ extractFromPythonSource,
+ PYTHON_GT_PACKAGES,
+ PYTHON_GT_DEPENDENCIES,
+ PYTHON_T_FUNCTION,
+ PYTHON_DECLARE_STATIC,
+ PYTHON_DECLARE_VAR,
+ PYTHON_METADATA_KWARGS,
+} from '../index.js';
+import type { ExtractionResult, ExtractionMetadata } from '../types.js';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const fixture = (name: string) =>
+ fs.readFileSync(path.join(__dirname, 'fixtures', name), 'utf8');
+
+describe('python-extractor', () => {
+ describe('extractFromPythonSource', () => {
+ it('extracts simple t() calls', async () => {
+ const { results, errors } = await extractFromPythonSource(
+ fixture('simple.py'),
+ 'simple.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(2);
+ expect(results[0].source).toBe('Hello world');
+ expect(results[0].dataFormat).toBe('ICU');
+ expect(results[1].source).toBe('Goodbye');
+ });
+
+ it('handles aliased imports', async () => {
+ const { results, errors } = await extractFromPythonSource(
+ fixture('aliased_import.py'),
+ 'aliased_import.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(1);
+ expect(results[0].source).toBe('Hello world');
+ });
+
+ it('extracts metadata kwargs (_id, _context, _maxChars)', async () => {
+ const { results, errors } = await extractFromPythonSource(
+ fixture('kwargs_metadata.py'),
+ 'kwargs_metadata.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(1);
+ expect(results[0].source).toBe('Hello');
+ expect(results[0].metadata.id).toBe('greeting');
+ expect(results[0].metadata.context).toBe('casual');
+ expect(results[0].metadata.maxChars).toBe(50);
+ });
+
+ it('returns empty results for files without GT imports', async () => {
+ const { results, errors } = await extractFromPythonSource(
+ fixture('no_gt_imports.py'),
+ 'no_gt_imports.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toEqual([]);
+ });
+
+ it('only extracts GT calls in files with mixed imports', async () => {
+ const { results, errors } = await extractFromPythonSource(
+ fixture('mixed_imports.py'),
+ 'mixed_imports.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(1);
+ expect(results[0].source).toBe('Hello from Flask');
+ });
+
+ it('extracts multiple calls from one file', async () => {
+ const { results, errors } = await extractFromPythonSource(
+ fixture('multiple_calls.py'),
+ 'multiple_calls.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(4);
+ expect(results.map((r) => r.source)).toEqual([
+ 'First',
+ 'Second',
+ 'Third',
+ 'Fourth',
+ ]);
+ expect(results[3].metadata.id).toBe('fourth');
+ expect(results[3].metadata.context).toBe('test');
+ });
+
+ it('works with gt_fastapi imports', async () => {
+ const { results, errors } = await extractFromPythonSource(
+ fixture('fastapi.py'),
+ 'fastapi.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(1);
+ expect(results[0].source).toBe('Hello from FastAPI');
+ });
+
+ it('reports errors for f-strings and non-static content', async () => {
+ const { results, errors } = await extractFromPythonSource(
+ fixture('errors.py'),
+ 'errors.py'
+ );
+ // Should still extract the valid string
+ expect(results).toHaveLength(1);
+ expect(results[0].source).toBe('Valid string');
+
+ // Should report errors for f-string and variable
+ expect(errors).toHaveLength(2);
+ expect(errors[0]).toContain('f-string');
+ expect(errors[1]).toContain('variable');
+ });
+
+ it('handles multiple function imports (t, msg)', async () => {
+ const { results, errors } = await extractFromPythonSource(
+ fixture('multi_import.py'),
+ 'multi_import.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(2);
+ expect(results[0].source).toBe('From t function');
+ expect(results[1].source).toBe('From msg function');
+ });
+
+ it('includes filePaths in metadata', async () => {
+ const { results } = await extractFromPythonSource(
+ fixture('simple.py'),
+ 'app/routes.py'
+ );
+ expect(results[0].metadata.filePaths).toEqual(['app/routes.py']);
+ });
+
+ it('handles empty source code', async () => {
+ const { results, errors } = await extractFromPythonSource('', 'empty.py');
+ expect(results).toEqual([]);
+ expect(errors).toEqual([]);
+ });
+
+ it('handles single-quoted strings', async () => {
+ const { results, errors } = await extractFromPythonSource(
+ `from gt_flask import t\nt('single quoted')`,
+ 'test.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(1);
+ expect(results[0].source).toBe('single quoted');
+ });
+
+ it('handles triple-quoted strings', async () => {
+ const { results, errors } = await extractFromPythonSource(
+ `from gt_flask import t\nt("""triple quoted""")`,
+ 'test.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(1);
+ expect(results[0].source).toBe('triple quoted');
+ });
+
+ it('handles empty strings', async () => {
+ const { results, errors } = await extractFromPythonSource(
+ `from gt_flask import t\nt("")`,
+ 'test.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(1);
+ expect(results[0].source).toBe('');
+ });
+
+ it('ignores non-translation GT imports like initialize_gt', async () => {
+ const code = `from gt_fastapi import t, initialize_gt, get_locale as get_locale_gt
+from fastapi import FastAPI
+
+app = FastAPI()
+
+initialize_gt(
+ app,
+ load_translations=load_translations,
+ get_locale=get_locale,
+)
+
+get_locale_gt()
+t("Hello, world!")`;
+ const { results, errors } = await extractFromPythonSource(
+ code,
+ 'main.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(1);
+ expect(results[0].source).toBe('Hello, world!');
+ });
+
+ it('extracts _max_chars with snake_case kwarg', async () => {
+ const code = `from gt_flask import t\nt("Hello", _max_chars=10)`;
+ const { results, errors } = await extractFromPythonSource(
+ code,
+ 'test.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(1);
+ expect(results[0].metadata.maxChars).toBe(10);
+ });
+ });
+
+ // ===== declare_static tests ===== //
+
+ describe('declare_static', () => {
+ it('expands simple ternary into 2 variants', async () => {
+ const { results, errors } = await extractFromPythonSource(
+ fixture('declare_static_ternary.py'),
+ 'test.py'
+ );
+ // First call: t(f"It is {declare_static('day' if is_day() else 'night')}!")
+ const ternaryResults = results.filter(
+ (r) => r.source === 'It is day!' || r.source === 'It is night!'
+ );
+ expect(ternaryResults).toHaveLength(2);
+ expect(ternaryResults[0].metadata.staticId).toBeDefined();
+ expect(ternaryResults[0].metadata.staticId).toBe(
+ ternaryResults[1].metadata.staticId
+ );
+ });
+
+ it('expands nested ternary into 3 variants', async () => {
+ const { results, errors } = await extractFromPythonSource(
+ fixture('declare_static_ternary.py'),
+ 'test.py'
+ );
+ const nestedResults = results.filter(
+ (r) => r.source === 'a' || r.source === 'b' || r.source === 'c'
+ );
+ expect(nestedResults).toHaveLength(3);
+ const staticId = nestedResults[0].metadata.staticId;
+ expect(staticId).toBeDefined();
+ expect(nestedResults.every((r) => r.metadata.staticId === staticId)).toBe(
+ true
+ );
+ });
+
+ it('handles plain string in declare_static', async () => {
+ const { results, errors } = await extractFromPythonSource(
+ fixture('declare_static_ternary.py'),
+ 'test.py'
+ );
+ const plainResult = results.find((r) => r.source === 'Hello world!');
+ expect(plainResult).toBeDefined();
+ expect(plainResult!.metadata.staticId).toBeDefined();
+ });
+
+ it('resolves local function returns in declare_static', async () => {
+ const { results, errors } = await extractFromPythonSource(
+ fixture('declare_static_func.py'),
+ 'test.py'
+ );
+ expect(errors).toEqual([]);
+ const morningResult = results.find((r) => r.source === 'It is morning!');
+ const eveningResult = results.find((r) => r.source === 'It is evening!');
+ expect(morningResult).toBeDefined();
+ expect(eveningResult).toBeDefined();
+ expect(morningResult!.metadata.staticId).toBe(
+ eveningResult!.metadata.staticId
+ );
+ });
+
+ it('resolves cross-file function in declare_static', async () => {
+ const helperPath = path.join(
+ __dirname,
+ 'fixtures',
+ 'declare_static_helper.py'
+ );
+ const code = `from gt_flask import t, declare_static
+from declare_static_helper import get_time
+t(f"It is {declare_static(get_time())}!")`;
+ const { results, errors } = await extractFromPythonSource(
+ code,
+ path.join(__dirname, 'fixtures', 'declare_static_crossfile.py')
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(2);
+ const sources = results.map((r) => r.source).sort();
+ expect(sources).toEqual(['It is evening!', 'It is morning!']);
+ expect(results[0].metadata.staticId).toBe(results[1].metadata.staticId);
+ });
+
+ it('handles concatenation with declare_static', async () => {
+ const { results, errors } = await extractFromPythonSource(
+ fixture('declare_static_concat.py'),
+ 'test.py'
+ );
+ // t("Hello " + declare_static("day" if x else "night") + "!")
+ const concatResults = results.filter(
+ (r) => r.source === 'Hello day!' || r.source === 'Hello night!'
+ );
+ expect(concatResults).toHaveLength(2);
+ expect(concatResults[0].metadata.staticId).toBe(
+ concatResults[1].metadata.staticId
+ );
+ });
+
+ it('produces cartesian product for multiple declare_statics', async () => {
+ const { results, errors } = await extractFromPythonSource(
+ fixture('declare_cartesian.py'),
+ 'test.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(4);
+ const sources = results.map((r) => r.source).sort();
+ expect(sources).toEqual([
+ 'bad day',
+ 'bad night',
+ 'good day',
+ 'good night',
+ ]);
+ // All share the same staticId
+ const staticId = results[0].metadata.staticId;
+ expect(staticId).toBeDefined();
+ expect(results.every((r) => r.metadata.staticId === staticId)).toBe(true);
+ });
+
+ it('preserves metadata kwargs with declare_static', async () => {
+ const code = `from gt_flask import t, declare_static
+t(f"It is {declare_static('day' if x else 'night')}", _id="time_msg", _context="greeting")`;
+ const { results, errors } = await extractFromPythonSource(
+ code,
+ 'test.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(2);
+ for (const r of results) {
+ expect(r.metadata.id).toBe('time_msg');
+ expect(r.metadata.context).toBe('greeting');
+ expect(r.metadata.staticId).toBeDefined();
+ }
+ });
+
+ it('simple t() still works without staticId', async () => {
+ const code = `from gt_flask import t\nt("Hello world")`;
+ const { results, errors } = await extractFromPythonSource(
+ code,
+ 'test.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(1);
+ expect(results[0].source).toBe('Hello world');
+ expect(results[0].metadata.staticId).toBeUndefined();
+ });
+
+ it('handles string concatenation inside declare_static', async () => {
+ const { results, errors } = await extractFromPythonSource(
+ fixture('declare_static_string_concat.py'),
+ 'test.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(1);
+ expect(results[0].source).toBe('Hello, ab!');
+ expect(results[0].metadata.staticId).toBeDefined();
+ });
+
+ it('resolves single-return function in declare_static', async () => {
+ const { results, errors } = await extractFromPythonSource(
+ fixture('declare_static_func_simple.py'),
+ 'test.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(1);
+ expect(results[0].source).toBe('Hello, !!');
+ expect(results[0].metadata.staticId).toBeDefined();
+ });
+
+ it('resolves function returning declare_var + concat in declare_static', async () => {
+ const { results, errors } = await extractFromPythonSource(
+ fixture('declare_var_in_func_only.py'),
+ 'test.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(1);
+ // Function returns: declare_var(name) + '!'
+ // → {_gt_, select, other {}} + "!" → sequence
+ // After indexVars: {_gt_1, select, other {}}
+ expect(results[0].source).toBe('Hello, {_gt_1, select, other {}}!!');
+ expect(results[0].metadata.staticId).toBeDefined();
+ });
+
+ it('resolves function with declare_var in ternary with declare_static', async () => {
+ const { results, errors } = await extractFromPythonSource(
+ fixture('declare_var_in_func.py'),
+ 'test.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(2);
+ const sources = results.map((r) => r.source).sort();
+ // Branch 1: get_name() → declare_var(name) + '!' → "{_gt_, select, other {}}!"
+ // Branch 2: 'fallback'
+ // After indexVars on branch 1: "{_gt_1, select, other {}}!"
+ expect(sources).toEqual([
+ 'Hello, fallback!',
+ 'Hello, {_gt_1, select, other {}}!!',
+ ]);
+ expect(results[0].metadata.staticId).toBe(results[1].metadata.staticId);
+ });
+
+ it('handles parenthesized expression wrapping static concat', async () => {
+ // t(("hello " + declare_static("world"))) — first arg is parenthesized_expression
+ const code = `from gt_flask import t, declare_static
+t(("hello " + declare_static("world")))`;
+ const { results, errors } = await extractFromPythonSource(
+ code,
+ 'test.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(1);
+ expect(results[0].source).toBe('hello world');
+ expect(results[0].metadata.staticId).toBeDefined();
+ });
+
+ it('handles parenthesized expression wrapping static ternary', async () => {
+ const code = `from gt_flask import t, declare_static
+t((declare_static("day" if x else "night")))`;
+ const { results, errors } = await extractFromPythonSource(
+ code,
+ 'test.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(2);
+ const sources = results.map((r) => r.source).sort();
+ expect(sources).toEqual(['day', 'night']);
+ });
+
+ it('handles deeply nested parentheses around static expression', async () => {
+ const code = `from gt_flask import t, declare_static
+t((((("hello " + declare_static("day" if x else "night"))))))`;
+ const { results, errors } = await extractFromPythonSource(
+ code,
+ 'test.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(2);
+ const sources = results.map((r) => r.source).sort();
+ expect(sources).toEqual(['hello day', 'hello night']);
+ });
+
+ it('handles declare_static with inline concat of ternary + string', async () => {
+ const code = `from gt_flask import t, declare_static
+t(f"Result: {declare_static(('yes' if x else 'no') + '!')}")`;
+ const { results, errors } = await extractFromPythonSource(
+ code,
+ 'test.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(2);
+ const sources = results.map((r) => r.source).sort();
+ expect(sources).toEqual(['Result: no!', 'Result: yes!']);
+ });
+
+ it('handles declare_static with declare_var nested directly', async () => {
+ const code = `from gt_flask import t, declare_static, declare_var
+t(f"Hello, {declare_static(declare_var(name) + '!')}")`;
+ const { results, errors } = await extractFromPythonSource(
+ code,
+ 'test.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(1);
+ // declare_var(name) + '!' → "{_gt_, select, other {}}" + "!" → "{_gt_, select, other {}}!"
+ // Full f-string: "Hello, {_gt_, select, other {}}!"
+ // After indexVars: "Hello, {_gt_1, select, other {}}!"
+ expect(results[0].source).toBe('Hello, {_gt_1, select, other {}}!');
+ });
+
+ it('handles aliased imports with functions returning conditionals and declare_var', async () => {
+ // This exercises:
+ // - aliased declare_static/declare_var imports
+ // - function resolution with conditional return expressions
+ // - declare_var inside a function return branch
+ // - cartesian product across two declare_static calls (2 × 2 = 4 variants)
+ const { results, errors } = await extractFromPythonSource(
+ fixture('declare_complex_aliased.py'),
+ 'test.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(4);
+
+ const sources = results.map((r) => r.source).sort();
+ expect(sources).toEqual([
+ 'The he is beautiful',
+ 'The he is {_gt_1, select, other {}}',
+ 'The she is beautiful',
+ 'The she is {_gt_1, select, other {}}',
+ ]);
+
+ // All 4 variants share the same staticId
+ const staticId = results[0].metadata.staticId;
+ expect(staticId).toBeDefined();
+ expect(results.every((r) => r.metadata.staticId === staticId)).toBe(true);
+ });
+ });
+
+ // ===== bare dot relative import tests ===== //
+
+ describe('bare dot relative import', () => {
+ it('resolves function re-exported via "from . import func" from __init__.py', async () => {
+ // Scenario: a package re-exports a function via bare dot import.
+ // pkg/__init__.py defines get_time()
+ // pkg/reexport.py has "from . import get_time" (bare dot → __init__.py)
+ // Main imports from reexport → findReExport sees moduleName='.' → null (BUG)
+ const pkgDir = path.join(__dirname, 'fixtures', 'dotpkg');
+ fs.mkdirSync(pkgDir, { recursive: true });
+ const initFile = path.join(pkgDir, '__init__.py');
+ const reexportFile = path.join(pkgDir, 'reexport.py');
+ fs.writeFileSync(
+ initFile,
+ `def get_time():\n if is_morning():\n return "morning"\n else:\n return "evening"\n`
+ );
+ fs.writeFileSync(reexportFile, 'from . import get_time\n');
+ try {
+ const code = `from gt_flask import t, declare_static
+from dotpkg.reexport import get_time
+t(f"It is {declare_static(get_time())}!")`;
+ const { results, errors } = await extractFromPythonSource(
+ code,
+ path.join(__dirname, 'fixtures', 'test_dot_import.py')
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(2);
+ const sources = results.map((r) => r.source).sort();
+ expect(sources).toEqual(['It is evening!', 'It is morning!']);
+ } finally {
+ fs.rmSync(pkgDir, { recursive: true });
+ }
+ });
+ });
+
+ // ===== re-export tests ===== //
+
+ describe('re-exports', () => {
+ it('follows single-level re-export to resolve functions', async () => {
+ // main imports get_gender from reexport_funcs.py,
+ // which re-exports from static_test_defs.py where it's defined
+ const code = `from gt_fastapi import t, declare_static as alias_declare_static
+from reexport_funcs import get_gender
+t(f"The {alias_declare_static(get_gender(variant))}")`;
+ const { results, errors } = await extractFromPythonSource(
+ code,
+ path.join(__dirname, 'fixtures', 'reexport_main.py')
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(2);
+ const sources = results.map((r) => r.source).sort();
+ expect(sources).toEqual(['The he', 'The she']);
+ });
+
+ it('follows re-export with declare_var in the definition file', async () => {
+ // get_adjective is defined in static_test_defs.py with alias_declare_var,
+ // re-exported through reexport_funcs.py
+ const code = `from gt_fastapi import t, declare_static as alias_declare_static
+from reexport_funcs import get_adjective
+t(f"She is {alias_declare_static(get_adjective(variant))}")`;
+ const { results, errors } = await extractFromPythonSource(
+ code,
+ path.join(__dirname, 'fixtures', 'reexport_main.py')
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(2);
+ const sources = results.map((r) => r.source).sort();
+ expect(sources).toEqual([
+ 'She is beautiful',
+ 'She is {_gt_1, select, other {}}',
+ ]);
+ });
+
+ it('follows two-level re-export chain', async () => {
+ // main → reexport_chain_top → reexport_chain_mid → static_test_defs
+ const code = `from gt_fastapi import t, declare_static as alias_declare_static
+from reexport_chain_top import get_gender
+t(f"The {alias_declare_static(get_gender(variant))}")`;
+ const { results, errors } = await extractFromPythonSource(
+ code,
+ path.join(__dirname, 'fixtures', 'reexport_chain_main.py')
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(2);
+ const sources = results.map((r) => r.source).sort();
+ expect(sources).toEqual(['The he', 'The she']);
+ });
+
+ it('handles full complex re-export scenario with cartesian product', async () => {
+ // The exact user scenario: main imports from reexport_funcs,
+ // definitions in static_test_defs with aliased declare_var
+ const code = `from gt_fastapi import t, declare_static as alias_declare_static
+from reexport_funcs import get_gender, get_adjective
+
+def get_string(variant):
+ return t(f'The {alias_declare_static(get_gender(variant))} is {alias_declare_static(get_adjective(variant))}')`;
+ const { results, errors } = await extractFromPythonSource(
+ code,
+ path.join(__dirname, 'fixtures', 'reexport_main.py')
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(4);
+ const sources = results.map((r) => r.source).sort();
+ expect(sources).toEqual([
+ 'The he is beautiful',
+ 'The he is {_gt_1, select, other {}}',
+ 'The she is beautiful',
+ 'The she is {_gt_1, select, other {}}',
+ ]);
+ const staticId = results[0].metadata.staticId;
+ expect(staticId).toBeDefined();
+ expect(results.every((r) => r.metadata.staticId === staticId)).toBe(true);
+ });
+
+ it('handles re-export with aliased name', async () => {
+ // Import with alias in re-export: from static_test_defs import get_gender as gg
+ const code = `from gt_fastapi import t, declare_static
+from reexport_alias import gg
+t(f"{declare_static(gg(v))}")`;
+ // Create aliased re-export inline
+ const aliasReexportPath = path.join(
+ __dirname,
+ 'fixtures',
+ 'reexport_alias.py'
+ );
+ fs.writeFileSync(
+ aliasReexportPath,
+ 'from static_test_defs import get_gender as gg\n'
+ );
+ try {
+ const { results, errors } = await extractFromPythonSource(
+ code,
+ path.join(__dirname, 'fixtures', 'reexport_alias_main.py')
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(2);
+ const sources = results.map((r) => r.source).sort();
+ expect(sources).toEqual(['he', 'she']);
+ } finally {
+ fs.unlinkSync(aliasReexportPath);
+ }
+ });
+ });
+
+ // ===== declare_var tests ===== //
+
+ describe('declare_var', () => {
+ it('produces ICU placeholder for basic declare_var', async () => {
+ const code = `from gt_flask import t, declare_var
+t(f"Hello {declare_var(name)}!")`;
+ const { results, errors } = await extractFromPythonSource(
+ code,
+ 'test.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(1);
+ // declareVar('') → {_gt_, select, other {}}
+ // indexVars → {_gt_1, select, other {}}
+ expect(results[0].source).toBe('Hello {_gt_1, select, other {}}!');
+ expect(results[0].metadata.staticId).toBeDefined();
+ });
+
+ it('produces ICU placeholder with _name kwarg', async () => {
+ const code = `from gt_flask import t, declare_var
+t(f"Hello {declare_var(name, _name='user')}!")`;
+ const { results, errors } = await extractFromPythonSource(
+ code,
+ 'test.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(1);
+ expect(results[0].source).toBe(
+ 'Hello {_gt_1, select, other {} _gt_var_name {user}}!'
+ );
+ });
+ });
+
+ // ===== mixed declare_static + declare_var tests ===== //
+
+ describe('mixed declare_static + declare_var', () => {
+ it('combines static variants with var placeholders', async () => {
+ const { results, errors } = await extractFromPythonSource(
+ fixture('declare_mixed.py'),
+ 'test.py'
+ );
+ expect(errors).toEqual([]);
+ expect(results).toHaveLength(2);
+ const sources = results.map((r) => r.source).sort();
+ // "day for {_gt_, select, other {}}" and "night for {_gt_, select, other {}}"
+ // After indexVars: {_gt_1, select, other {}}
+ expect(sources).toEqual([
+ 'day for {_gt_1, select, other {}}',
+ 'night for {_gt_1, select, other {}}',
+ ]);
+ expect(results[0].metadata.staticId).toBe(results[1].metadata.staticId);
+ });
+ });
+
+ // ===== resolveStaticBinaryOperator edge cases ===== //
+
+ describe('static binary operator with unusual operand types', () => {
+ it('handles subscript operand in declare_static concat', async () => {
+ // obj["key"] + "!" inside declare_static — subscript is not in the
+ // exclusion list, so the old code misreports "unsupported binary operator"
+ const code = `from gt_flask import t, declare_static
+t(f"{declare_static(obj['key'] + '!')}")`;
+ const { errors } = await extractFromPythonSource(code, 'test.py');
+ expect(errors.length).toBeGreaterThan(0);
+ // Should complain about the subscript operand type, NOT the operator
+ expect(errors.join(' ')).not.toContain('unsupported binary operator');
+ expect(errors.join(' ')).toContain('subscript');
+ });
+
+ it('handles attribute operand in declare_static concat', async () => {
+ // obj.attr + "!" inside declare_static — attribute node not in exclusion list
+ const code = `from gt_flask import t, declare_static
+t(f"{declare_static(obj.attr + '!')}")`;
+ const { errors } = await extractFromPythonSource(code, 'test.py');
+ expect(errors.length).toBeGreaterThan(0);
+ expect(errors.join(' ')).not.toContain('unsupported binary operator');
+ expect(errors.join(' ')).toContain('attribute');
+ });
+
+ it('correctly rejects non-plus operator in static context', async () => {
+ // * operator should mention the operator in the error
+ const code = `from gt_flask import t, declare_static
+t(f"{declare_static('a' * 3)}")`;
+ const { errors } = await extractFromPythonSource(code, 'test.py');
+ expect(errors.length).toBeGreaterThan(0);
+ });
+ });
+
+ // ===== stringNode tests ===== //
+
+ describe('stringNode', () => {
+ // These are tested indirectly through the integration tests above,
+ // but let's also test nodeToStrings directly
+ it('nodeToStrings is importable and works', async () => {
+ const { nodeToStrings } = await import('../stringNode.js');
+
+ expect(nodeToStrings({ type: 'text', text: 'hello' })).toEqual(['hello']);
+
+ expect(
+ nodeToStrings({
+ type: 'sequence',
+ nodes: [
+ { type: 'text', text: 'Hello ' },
+ { type: 'text', text: 'world' },
+ ],
+ })
+ ).toEqual(['Hello world']);
+
+ expect(
+ nodeToStrings({
+ type: 'choice',
+ nodes: [
+ { type: 'text', text: 'day' },
+ { type: 'text', text: 'night' },
+ ],
+ })
+ ).toEqual(['day', 'night']);
+
+ // Cartesian product
+ expect(
+ nodeToStrings({
+ type: 'sequence',
+ nodes: [
+ {
+ type: 'choice',
+ nodes: [
+ { type: 'text', text: 'a' },
+ { type: 'text', text: 'b' },
+ ],
+ },
+ { type: 'text', text: ' and ' },
+ {
+ type: 'choice',
+ nodes: [
+ { type: 'text', text: 'x' },
+ { type: 'text', text: 'y' },
+ ],
+ },
+ ],
+ })
+ ).toEqual(['a and x', 'a and y', 'b and x', 'b and y']);
+
+ // Null
+ expect(nodeToStrings(null)).toEqual([]);
+
+ // Deduplication
+ expect(
+ nodeToStrings({
+ type: 'choice',
+ nodes: [
+ { type: 'text', text: 'same' },
+ { type: 'text', text: 'same' },
+ ],
+ })
+ ).toEqual(['same']);
+ });
+ });
+
+ describe('types', () => {
+ it('ExtractionResult type is usable', () => {
+ const result: ExtractionResult = {
+ dataFormat: 'ICU',
+ source: 'Hello',
+ metadata: { id: 'greeting' },
+ };
+ expect(result.dataFormat).toBe('ICU');
+ expect(result.source).toBe('Hello');
+ expect(result.metadata.id).toBe('greeting');
+ });
+
+ it('ExtractionMetadata supports all optional fields', () => {
+ const metadata: ExtractionMetadata = {
+ id: 'test',
+ context: 'casual',
+ maxChars: 100,
+ filePaths: ['file.py'],
+ staticId: 'static-1',
+ };
+ expect(metadata.id).toBe('test');
+ expect(metadata.context).toBe('casual');
+ expect(metadata.maxChars).toBe(100);
+ expect(metadata.filePaths).toEqual(['file.py']);
+ expect(metadata.staticId).toBe('static-1');
+ });
+ });
+
+ describe('constants', () => {
+ it('exports PYTHON_GT_PACKAGES', () => {
+ expect(PYTHON_GT_PACKAGES).toEqual(['gt_flask', 'gt_fastapi']);
+ });
+
+ it('exports PYTHON_GT_DEPENDENCIES', () => {
+ expect(PYTHON_GT_DEPENDENCIES).toEqual(['gt-flask', 'gt-fastapi']);
+ });
+
+ it('exports PYTHON_T_FUNCTION', () => {
+ expect(PYTHON_T_FUNCTION).toBe('t');
+ });
+
+ it('exports PYTHON_DECLARE_STATIC', () => {
+ expect(PYTHON_DECLARE_STATIC).toBe('declare_static');
+ });
+
+ it('exports PYTHON_DECLARE_VAR', () => {
+ expect(PYTHON_DECLARE_VAR).toBe('declare_var');
+ });
+
+ it('exports PYTHON_METADATA_KWARGS', () => {
+ expect(PYTHON_METADATA_KWARGS).toEqual({
+ _id: 'id',
+ _context: 'context',
+ _max_chars: 'maxChars',
+ });
+ });
+ });
+});
diff --git a/packages/python-extractor/src/constants.ts b/packages/python-extractor/src/constants.ts
new file mode 100644
index 000000000..5c23bcb3f
--- /dev/null
+++ b/packages/python-extractor/src/constants.ts
@@ -0,0 +1,18 @@
+export const PYTHON_GT_PACKAGES = ['gt_flask', 'gt_fastapi'] as const;
+export const PYTHON_GT_DEPENDENCIES = ['gt-flask', 'gt-fastapi'] as const;
+export const PYTHON_T_FUNCTION = 't';
+export const PYTHON_MSG_FUNCTION = 'msg';
+export const PYTHON_DECLARE_STATIC = 'declare_static';
+export const PYTHON_DECLARE_VAR = 'declare_var';
+/** These imported names are tracked (translation functions + static helpers) */
+export const PYTHON_TRANSLATION_FUNCTIONS = [
+ 't',
+ 'msg',
+ 'declare_static',
+ 'declare_var',
+] as const;
+export const PYTHON_METADATA_KWARGS = {
+ _id: 'id',
+ _context: 'context',
+ _max_chars: 'maxChars',
+} as const;
diff --git a/packages/python-extractor/src/extractCalls.ts b/packages/python-extractor/src/extractCalls.ts
new file mode 100644
index 000000000..8d11f7a06
--- /dev/null
+++ b/packages/python-extractor/src/extractCalls.ts
@@ -0,0 +1,314 @@
+import type { SyntaxNode } from './parser.js';
+import type { ImportAlias } from './extractImports.js';
+import { PYTHON_METADATA_KWARGS } from './constants.js';
+import {
+ containsStaticCalls,
+ parseStringExpression,
+} from './parseStringExpression.js';
+import { nodeToStrings } from './stringNode.js';
+import { indexVars } from 'generaltranslation/internal';
+import { randomUUID } from 'node:crypto';
+
+export type RawTranslationCall = {
+ source: string;
+ id?: string;
+ context?: string;
+ maxChars?: number;
+ staticId?: string;
+ line: number;
+ column: number;
+};
+
+/**
+ * Extracts translation function calls from a Python AST.
+ * Walks all `call` nodes and checks if they reference a tracked import.
+ */
+export async function extractCalls(
+ rootNode: SyntaxNode,
+ imports: ImportAlias[],
+ filePath: string
+): Promise<{
+ calls: RawTranslationCall[];
+ errors: string[];
+ warnings: string[];
+}> {
+ const calls: RawTranslationCall[] = [];
+ const errors: string[] = [];
+ const warnings: string[] = [];
+
+ // Only track t/msg as translation functions (not declare_static/declare_var)
+ const trackedNames = new Set(
+ imports
+ .filter(
+ (imp) =>
+ imp.originalName !== 'declare_static' &&
+ imp.originalName !== 'declare_var'
+ )
+ .map((imp) => imp.localName)
+ );
+ if (trackedNames.size === 0) return { calls, errors, warnings };
+
+ await walkCalls(
+ rootNode,
+ trackedNames,
+ imports,
+ filePath,
+ calls,
+ errors,
+ warnings
+ );
+
+ return { calls, errors, warnings };
+}
+
+async function walkCalls(
+ node: SyntaxNode,
+ trackedNames: Set,
+ imports: ImportAlias[],
+ filePath: string,
+ calls: RawTranslationCall[],
+ errors: string[],
+ warnings: string[]
+): Promise {
+ if (node.type === 'call') {
+ const funcNode = node.childForFieldName('function');
+ if (
+ funcNode &&
+ funcNode.type === 'identifier' &&
+ trackedNames.has(funcNode.text)
+ ) {
+ await processCall(node, imports, filePath, calls, errors, warnings);
+ }
+ }
+
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child)
+ await walkCalls(
+ child,
+ trackedNames,
+ imports,
+ filePath,
+ calls,
+ errors,
+ warnings
+ );
+ }
+}
+
+async function processCall(
+ callNode: SyntaxNode,
+ imports: ImportAlias[],
+ filePath: string,
+ calls: RawTranslationCall[],
+ errors: string[],
+ _warnings: string[]
+): Promise {
+ const argsNode = callNode.childForFieldName('arguments');
+ if (!argsNode) {
+ errors.push(`${locationStr(callNode)}: translation call has no arguments`);
+ return;
+ }
+
+ // Find first positional argument (skip punctuation)
+ let firstArg: SyntaxNode | null = null;
+ for (let i = 0; i < argsNode.childCount; i++) {
+ const child = argsNode.child(i);
+ if (
+ child &&
+ child.type !== '(' &&
+ child.type !== ')' &&
+ child.type !== ',' &&
+ child.type !== 'keyword_argument'
+ ) {
+ firstArg = child;
+ break;
+ }
+ }
+
+ if (!firstArg) {
+ errors.push(
+ `${locationStr(callNode)}: translation call has no positional argument`
+ );
+ return;
+ }
+
+ // Check if this expression contains declare_static/declare_var
+ const hasStaticHelpers =
+ (firstArg.type === 'string' &&
+ isFString(firstArg) &&
+ containsStaticCalls(firstArg, imports)) ||
+ (firstArg.type === 'binary_operator' &&
+ containsStaticCalls(firstArg, imports)) ||
+ (firstArg.type === 'call' && containsStaticCalls(firstArg, imports)) ||
+ (firstArg.type === 'parenthesized_expression' &&
+ containsStaticCalls(firstArg, imports));
+
+ if (hasStaticHelpers) {
+ // Compound expression path: parse into StringNode tree
+ const rootNode = callNode.tree?.rootNode;
+ if (!rootNode) {
+ errors.push(`${locationStr(callNode)}: could not access AST root`);
+ return;
+ }
+
+ const stringNode = await parseStringExpression(firstArg, {
+ rootNode,
+ imports,
+ filePath,
+ errors,
+ });
+
+ if (!stringNode) return;
+
+ const strings = nodeToStrings(stringNode).map(indexVars);
+ if (strings.length === 0) {
+ errors.push(`${locationStr(callNode)}: no string variants produced`);
+ return;
+ }
+
+ const metadata = extractKwargs(argsNode, errors, callNode);
+ const staticId = `static-temp-id-${randomUUID()}`;
+
+ for (const source of strings) {
+ calls.push({
+ source,
+ ...metadata,
+ staticId,
+ line: callNode.startPosition.row + 1,
+ column: callNode.startPosition.column,
+ });
+ }
+ return;
+ }
+
+ // Simple path: validate first argument is a plain string literal
+ if (firstArg.type !== 'string') {
+ if (firstArg.type === 'identifier') {
+ errors.push(
+ `${locationStr(callNode)}: translation call uses a variable "${firstArg.text}" instead of a string literal`
+ );
+ } else if (firstArg.type === 'concatenated_string') {
+ errors.push(
+ `${locationStr(callNode)}: translation call uses concatenated strings — use a single string literal`
+ );
+ } else {
+ errors.push(
+ `${locationStr(callNode)}: translation call first argument must be a string literal, got "${firstArg.type}"`
+ );
+ }
+ return;
+ }
+
+ // Check for f-strings (without declare_static/declare_var)
+ if (isFString(firstArg)) {
+ errors.push(
+ `${locationStr(callNode)}: translation call uses an f-string — use a plain string literal or declare_static()/declare_var()`
+ );
+ return;
+ }
+
+ const source = extractStringContent(firstArg);
+ if (source === undefined) {
+ errors.push(`${locationStr(callNode)}: could not extract string content`);
+ return;
+ }
+
+ // Extract keyword arguments
+ const metadata = extractKwargs(argsNode, errors, callNode);
+
+ calls.push({
+ source,
+ ...metadata,
+ line: callNode.startPosition.row + 1,
+ column: callNode.startPosition.column,
+ });
+}
+
+function extractKwargs(
+ argsNode: SyntaxNode,
+ errors: string[],
+ callNode: SyntaxNode
+): { id?: string; context?: string; maxChars?: number } {
+ const result: { id?: string; context?: string; maxChars?: number } = {};
+
+ for (let i = 0; i < argsNode.childCount; i++) {
+ const child = argsNode.child(i);
+ if (!child || child.type !== 'keyword_argument') continue;
+
+ const nameNode = child.childForFieldName('name');
+ const valueNode = child.childForFieldName('value');
+ if (!nameNode || !valueNode) continue;
+
+ const kwargName = nameNode.text;
+ const metadataKey = (
+ PYTHON_METADATA_KWARGS as Record
+ )[kwargName];
+ if (!metadataKey) continue;
+
+ if (metadataKey === 'maxChars') {
+ if (valueNode.type === 'integer') {
+ result.maxChars = parseInt(valueNode.text, 10);
+ } else {
+ errors.push(
+ `${locationStr(callNode)}: _max_chars must be an integer literal`
+ );
+ }
+ } else {
+ if (valueNode.type === 'string' && !isFString(valueNode)) {
+ const value = extractStringContent(valueNode);
+ if (value !== undefined) {
+ if (metadataKey === 'id') result.id = value;
+ else if (metadataKey === 'context') result.context = value;
+ }
+ } else {
+ errors.push(
+ `${locationStr(callNode)}: _${metadataKey} must be a string literal`
+ );
+ }
+ }
+ }
+
+ return result;
+}
+
+function isFString(stringNode: SyntaxNode): boolean {
+ // Check if string_start begins with 'f' or 'F'
+ for (let i = 0; i < stringNode.childCount; i++) {
+ const child = stringNode.child(i);
+ if (child && child.type === 'string_start') {
+ return /^[fF]/.test(child.text);
+ }
+ // Also check for interpolation children (hallmark of f-strings)
+ if (child && child.type === 'interpolation') {
+ return true;
+ }
+ }
+ return false;
+}
+
+function extractStringContent(stringNode: SyntaxNode): string | undefined {
+ // Look for string_content child
+ for (let i = 0; i < stringNode.childCount; i++) {
+ const child = stringNode.child(i);
+ if (child && child.type === 'string_content') {
+ return child.text;
+ }
+ }
+
+ // Empty string — no string_content child, but has string_start and string_end
+ let hasStart = false;
+ let hasEnd = false;
+ for (let i = 0; i < stringNode.childCount; i++) {
+ const child = stringNode.child(i);
+ if (child?.type === 'string_start') hasStart = true;
+ if (child?.type === 'string_end') hasEnd = true;
+ }
+ if (hasStart && hasEnd) return '';
+
+ return undefined;
+}
+
+function locationStr(node: SyntaxNode): string {
+ return `line ${node.startPosition.row + 1}, col ${node.startPosition.column}`;
+}
diff --git a/packages/python-extractor/src/extractImports.ts b/packages/python-extractor/src/extractImports.ts
new file mode 100644
index 000000000..0c4ee595e
--- /dev/null
+++ b/packages/python-extractor/src/extractImports.ts
@@ -0,0 +1,99 @@
+import type { SyntaxNode } from './parser.js';
+import {
+ PYTHON_GT_PACKAGES,
+ PYTHON_TRANSLATION_FUNCTIONS,
+} from './constants.js';
+
+export type ImportAlias = {
+ /** The local name used in the source file (e.g. "translate" for `import t as translate`) */
+ localName: string;
+ /** The original imported name (e.g. "t") */
+ originalName: string;
+ /** The package it was imported from (e.g. "gt_flask") */
+ packageName: string;
+};
+
+/**
+ * Extracts GT-related imports from a Python AST.
+ *
+ * Handles:
+ * - `from gt_flask import t`
+ * - `from gt_flask import t as translate`
+ * - `from gt_flask import t, msg`
+ */
+export function extractImports(rootNode: SyntaxNode): ImportAlias[] {
+ const aliases: ImportAlias[] = [];
+
+ for (let i = 0; i < rootNode.childCount; i++) {
+ const node = rootNode.child(i);
+ if (!node || node.type !== 'import_from_statement') continue;
+
+ const moduleName = getModuleName(node);
+ if (!moduleName || !isGtPackage(moduleName)) continue;
+
+ // Collect all imported names from this statement
+ for (let j = 0; j < node.childCount; j++) {
+ const child = node.child(j);
+ if (!child) continue;
+
+ if (child.type === 'aliased_import') {
+ // `from gt_flask import t as translate`
+ const nameNode = child.childForFieldName('name');
+ const aliasNode = child.childForFieldName('alias');
+ const originalName = nameNode ? getIdentifierText(nameNode) : undefined;
+ const localName = aliasNode ? aliasNode.text : originalName;
+ if (originalName && localName && isTranslationFunction(originalName)) {
+ aliases.push({ localName, originalName, packageName: moduleName });
+ }
+ } else if (child.type === 'dotted_name') {
+ // Skip the module name itself (first dotted_name is the module)
+ const text = child.text;
+ if (text === moduleName) continue;
+ // `from gt_flask import t` — only track translation functions
+ if (isTranslationFunction(text)) {
+ aliases.push({
+ localName: text,
+ originalName: text,
+ packageName: moduleName,
+ });
+ }
+ }
+ }
+ }
+
+ return aliases;
+}
+
+function getModuleName(importNode: SyntaxNode): string | undefined {
+ const moduleNode = importNode.childForFieldName('module_name');
+ if (moduleNode) return moduleNode.text;
+
+ // Fallback: find the first dotted_name child (before 'import' keyword)
+ for (let i = 0; i < importNode.childCount; i++) {
+ const child = importNode.child(i);
+ if (!child) continue;
+ if (child.type === 'import') break; // reached the 'import' keyword
+ if (child.type === 'dotted_name') return child.text;
+ }
+ return undefined;
+}
+
+function getIdentifierText(node: SyntaxNode): string | undefined {
+ if (node.type === 'identifier') return node.text;
+ if (node.type === 'dotted_name') {
+ // Get the last identifier in a dotted name
+ for (let i = node.childCount - 1; i >= 0; i--) {
+ const child = node.child(i);
+ if (child && child.type === 'identifier') return child.text;
+ }
+ }
+ return node.text;
+}
+
+function isGtPackage(name: string): boolean {
+ return (PYTHON_GT_PACKAGES as readonly string[]).includes(name);
+}
+
+function isTranslationFunction(name: string): boolean {
+ return (PYTHON_TRANSLATION_FUNCTIONS as readonly string[]).includes(name);
+}
diff --git a/packages/python-extractor/src/index.ts b/packages/python-extractor/src/index.ts
new file mode 100644
index 000000000..3841f7152
--- /dev/null
+++ b/packages/python-extractor/src/index.ts
@@ -0,0 +1,72 @@
+import type { ExtractionResult } from './types.js';
+import { getParser } from './parser.js';
+import { extractImports } from './extractImports.js';
+import { extractCalls } from './extractCalls.js';
+
+export type { ExtractionResult, ExtractionMetadata } from './types.js';
+export type { ImportAlias } from './extractImports.js';
+export {
+ PYTHON_GT_PACKAGES,
+ PYTHON_GT_DEPENDENCIES,
+ PYTHON_T_FUNCTION,
+ PYTHON_MSG_FUNCTION,
+ PYTHON_DECLARE_STATIC,
+ PYTHON_DECLARE_VAR,
+ PYTHON_TRANSLATION_FUNCTIONS,
+ PYTHON_METADATA_KWARGS,
+} from './constants.js';
+
+export async function extractFromPythonSource(
+ sourceCode: string,
+ filePath: string
+): Promise<{
+ results: ExtractionResult[];
+ errors: string[];
+ warnings: string[];
+}> {
+ const parser = await getParser();
+ const tree = parser.parse(sourceCode);
+ if (!tree) {
+ return {
+ results: [],
+ errors: [`Failed to parse ${filePath}`],
+ warnings: [],
+ };
+ }
+
+ // Step 1: Extract GT imports
+ const imports = extractImports(tree.rootNode);
+ if (imports.length === 0) {
+ return { results: [], errors: [], warnings: [] };
+ }
+
+ // Step 2: Extract translation calls
+ const { calls, errors, warnings } = await extractCalls(
+ tree.rootNode,
+ imports,
+ filePath
+ );
+
+ // Step 3: Map to ExtractionResult
+ const results: ExtractionResult[] = calls.map((call) => ({
+ dataFormat: 'ICU' as const,
+ source: call.source,
+ metadata: {
+ ...(call.id && { id: call.id }),
+ ...(call.context && { context: call.context }),
+ ...(call.maxChars != null && { maxChars: call.maxChars }),
+ ...(call.staticId && { staticId: call.staticId }),
+ filePaths: [filePath],
+ },
+ }));
+
+ return {
+ results,
+ errors: prefixErrors(errors, filePath),
+ warnings: prefixErrors(warnings, filePath),
+ };
+}
+
+function prefixErrors(messages: string[], filePath: string): string[] {
+ return messages.map((msg) => `${filePath}: ${msg}`);
+}
diff --git a/packages/python-extractor/src/parseStringExpression.ts b/packages/python-extractor/src/parseStringExpression.ts
new file mode 100644
index 000000000..c8b87d0af
--- /dev/null
+++ b/packages/python-extractor/src/parseStringExpression.ts
@@ -0,0 +1,716 @@
+import type { SyntaxNode } from './parser.js';
+import type { StringNode } from './stringNode.js';
+import type { ImportAlias } from './extractImports.js';
+import { PYTHON_DECLARE_STATIC, PYTHON_DECLARE_VAR } from './constants.js';
+import {
+ resolveFunctionInCurrentFile,
+ resolveFunctionInFile,
+} from './resolveFunctionVariants.js';
+import { extractImports } from './extractImports.js';
+import { resolveImportPath } from './resolveImport.js';
+import { declareVar } from 'generaltranslation/internal';
+
+type ParseContext = {
+ rootNode: SyntaxNode;
+ imports: ImportAlias[];
+ filePath: string;
+ errors: string[];
+};
+
+/**
+ * Checks if an expression contains declare_static or declare_var calls.
+ */
+export function containsStaticCalls(
+ node: SyntaxNode,
+ imports: ImportAlias[]
+): boolean {
+ const staticNames = getStaticImportNames(imports);
+ if (staticNames.size === 0) return false;
+ return hasStaticCallRecursive(node, staticNames);
+}
+
+function hasStaticCallRecursive(node: SyntaxNode, names: Set): boolean {
+ if (node.type === 'call') {
+ const funcNode = node.childForFieldName('function');
+ if (
+ funcNode &&
+ funcNode.type === 'identifier' &&
+ names.has(funcNode.text)
+ ) {
+ return true;
+ }
+ }
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child && hasStaticCallRecursive(child, names)) return true;
+ }
+ return false;
+}
+
+/**
+ * Parses the first argument of t() into a StringNode tree.
+ * Handles: plain strings, f-strings with declare_static/declare_var,
+ * binary + concatenation, and standalone declare_static calls.
+ */
+export async function parseStringExpression(
+ node: SyntaxNode,
+ ctx: ParseContext
+): Promise {
+ // Parenthesized expression: unwrap and recurse
+ if (node.type === 'parenthesized_expression') {
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child && child.type !== '(' && child.type !== ')') {
+ return parseStringExpression(child, ctx);
+ }
+ }
+ return null;
+ }
+
+ // Plain string (no f-string)
+ if (node.type === 'string' && !isFString(node)) {
+ const content = extractStringContent(node);
+ if (content === undefined) return null;
+ return { type: 'text', text: content };
+ }
+
+ // F-string with interpolations
+ if (node.type === 'string' && isFString(node)) {
+ return parseFString(node, ctx);
+ }
+
+ // Binary operator: string concatenation with +
+ if (node.type === 'binary_operator') {
+ return parseBinaryOperator(node, ctx);
+ }
+
+ // Standalone call: declare_static(...)
+ if (node.type === 'call') {
+ const funcNode = node.childForFieldName('function');
+ if (funcNode && funcNode.type === 'identifier') {
+ const originalName = getOriginalImportName(funcNode.text, ctx.imports);
+ if (originalName === PYTHON_DECLARE_STATIC) {
+ return resolveDeclareStaticArg(node, ctx);
+ }
+ if (originalName === PYTHON_DECLARE_VAR) {
+ return resolveDeclareVarArg(node, ctx);
+ }
+ }
+ }
+
+ ctx.errors.push(
+ `${locationStr(node)}: unsupported expression type "${node.type}" in translation call`
+ );
+ return null;
+}
+
+/**
+ * Parses an f-string into a StringNode tree.
+ * string_content → text nodes, interpolation → check for declare_static/declare_var
+ */
+async function parseFString(
+ node: SyntaxNode,
+ ctx: ParseContext
+): Promise {
+ const parts: StringNode[] = [];
+
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (!child) continue;
+
+ if (child.type === 'string_content') {
+ if (child.text.length > 0) {
+ parts.push({ type: 'text', text: child.text });
+ }
+ continue;
+ }
+
+ if (child.type === 'interpolation') {
+ const result = await parseInterpolation(child, ctx);
+ if (result) {
+ parts.push(result);
+ }
+ continue;
+ }
+
+ // Skip string_start, string_end, etc.
+ }
+
+ if (parts.length === 0) return { type: 'text', text: '' };
+ if (parts.length === 1) return parts[0];
+ return { type: 'sequence', nodes: parts };
+}
+
+/**
+ * Parses an interpolation within an f-string.
+ * Must be a declare_static() or declare_var() call.
+ */
+async function parseInterpolation(
+ interpNode: SyntaxNode,
+ ctx: ParseContext
+): Promise {
+ // Find the expression inside the interpolation (skip { and })
+ let expr: SyntaxNode | null = null;
+ for (let i = 0; i < interpNode.childCount; i++) {
+ const child = interpNode.child(i);
+ if (
+ child &&
+ child.type !== '{' &&
+ child.type !== '}' &&
+ child.type !== 'type_conversion' &&
+ child.type !== 'format_specifier'
+ ) {
+ expr = child;
+ break;
+ }
+ }
+
+ if (!expr) {
+ ctx.errors.push(
+ `${locationStr(interpNode)}: empty interpolation in f-string`
+ );
+ return null;
+ }
+
+ if (expr.type === 'call') {
+ const funcNode = expr.childForFieldName('function');
+ if (funcNode && funcNode.type === 'identifier') {
+ const originalName = getOriginalImportName(funcNode.text, ctx.imports);
+ if (originalName === PYTHON_DECLARE_STATIC) {
+ return resolveDeclareStaticArg(expr, ctx);
+ }
+ if (originalName === PYTHON_DECLARE_VAR) {
+ return resolveDeclareVarArg(expr, ctx);
+ }
+ }
+ }
+
+ // Not a declare_static/declare_var call — error
+ ctx.errors.push(
+ `${locationStr(interpNode)}: f-string interpolation must use declare_static() or declare_var(), got "${expr.text}"`
+ );
+ return null;
+}
+
+/**
+ * Parses binary + concatenation into a sequence node.
+ */
+async function parseBinaryOperator(
+ node: SyntaxNode,
+ ctx: ParseContext
+): Promise {
+ const left = node.childForFieldName('left');
+ const operator = node.childForFieldName('operator');
+ const right = node.childForFieldName('right');
+
+ if (!left || !right) {
+ ctx.errors.push(`${locationStr(node)}: binary operator missing operands`);
+ return null;
+ }
+
+ // Verify it's a + operator
+ if (operator && operator.text !== '+') {
+ ctx.errors.push(
+ `${locationStr(node)}: unsupported binary operator "${operator.text}" in translation call`
+ );
+ return null;
+ }
+
+ const leftNode = await parseStringExpression(left, ctx);
+ const rightNode = await parseStringExpression(right, ctx);
+
+ if (!leftNode || !rightNode) return null;
+
+ // Flatten nested sequences
+ const parts: StringNode[] = [];
+ if (leftNode.type === 'sequence') {
+ parts.push(...leftNode.nodes);
+ } else {
+ parts.push(leftNode);
+ }
+ if (rightNode.type === 'sequence') {
+ parts.push(...rightNode.nodes);
+ } else {
+ parts.push(rightNode);
+ }
+
+ return { type: 'sequence', nodes: parts };
+}
+
+/**
+ * Resolves the argument of a declare_static() call into a StringNode.
+ * Handles: string literals, ternary expressions, function calls.
+ */
+async function resolveDeclareStaticArg(
+ callNode: SyntaxNode,
+ ctx: ParseContext
+): Promise {
+ const arg = getFirstPositionalArg(callNode);
+ if (!arg) {
+ ctx.errors.push(
+ `${locationStr(callNode)}: declare_static() requires an argument`
+ );
+ return null;
+ }
+
+ return resolveStaticValue(arg, ctx);
+}
+
+/**
+ * Resolves a value expression that should produce string variants.
+ * Handles: string literals, ternary, function calls, binary concat,
+ * and declare_var() calls (nested inside declare_static).
+ */
+async function resolveStaticValue(
+ node: SyntaxNode,
+ ctx: ParseContext
+): Promise {
+ // Parenthesized expression: unwrap and recurse
+ if (node.type === 'parenthesized_expression') {
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child && child.type !== '(' && child.type !== ')') {
+ return resolveStaticValue(child, ctx);
+ }
+ }
+ return null;
+ }
+
+ // String literal
+ if (node.type === 'string' && !isFString(node)) {
+ const content = extractStringContent(node);
+ if (content === undefined) return null;
+ return { type: 'text', text: content };
+ }
+
+ // Ternary / conditional expression: "day" if cond else "night"
+ if (node.type === 'conditional_expression') {
+ return resolveConditional(node, ctx);
+ }
+
+ // Binary operator: string concatenation with +
+ if (node.type === 'binary_operator') {
+ return resolveStaticBinaryOperator(node, ctx);
+ }
+
+ // Function call — could be a user function or declare_var()
+ if (node.type === 'call') {
+ const funcNode = node.childForFieldName('function');
+ if (funcNode && funcNode.type === 'identifier') {
+ const originalName = getOriginalImportName(funcNode.text, ctx.imports);
+ if (originalName === PYTHON_DECLARE_VAR) {
+ return resolveDeclareVarArg(node, ctx);
+ }
+ }
+ return resolveFunctionCall(node, ctx);
+ }
+
+ ctx.errors.push(
+ `${locationStr(node)}: unsupported declare_static argument type "${node.type}"`
+ );
+ return null;
+}
+
+/**
+ * Handles binary + concatenation within a static value context.
+ */
+async function resolveStaticBinaryOperator(
+ node: SyntaxNode,
+ ctx: ParseContext
+): Promise {
+ const left = node.childForFieldName('left');
+ const right = node.childForFieldName('right');
+
+ if (!left || !right) {
+ ctx.errors.push(`${locationStr(node)}: binary operator missing operands`);
+ return null;
+ }
+
+ // Verify it's a + operator
+ const operator = node.childForFieldName('operator');
+ if (operator && operator.text !== '+') {
+ ctx.errors.push(
+ `${locationStr(node)}: unsupported binary operator "${operator.text}" in static expression`
+ );
+ return null;
+ }
+
+ const leftNode = await resolveStaticValue(left, ctx);
+ const rightNode = await resolveStaticValue(right, ctx);
+
+ if (!leftNode || !rightNode) return null;
+
+ // Flatten nested sequences
+ const parts: StringNode[] = [];
+ if (leftNode.type === 'sequence') {
+ parts.push(...leftNode.nodes);
+ } else {
+ parts.push(leftNode);
+ }
+ if (rightNode.type === 'sequence') {
+ parts.push(...rightNode.nodes);
+ } else {
+ parts.push(rightNode);
+ }
+
+ return { type: 'sequence', nodes: parts };
+}
+
+/**
+ * Resolves a Python conditional expression (ternary):
+ * "day" if cond else "night"
+ * tree-sitter: conditional_expression → [consequent, if, condition, else, alternate]
+ */
+async function resolveConditional(
+ node: SyntaxNode,
+ ctx: ParseContext
+): Promise {
+ // In Python's tree-sitter, the conditional_expression fields are:
+ // body = consequent (the value if true)
+ // condition = the test
+ // alternative = the else value (named 'alternative' field)
+ // But field names vary by tree-sitter version. Let's use positional children.
+ // Structure: consequent "if" condition "else" alternative
+
+ // The tree-sitter Python grammar uses named children:
+ // body (first expression), if keyword, condition, else keyword, alternative
+ // But let's find them by field name first, then fall back to positional.
+
+ // Try using children directly: first non-keyword child is consequent,
+ // child after "else" keyword is alternate
+ let consequent: SyntaxNode | null = null;
+ let alternate: SyntaxNode | null = null;
+ let seenElse = false;
+
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (!child) continue;
+
+ if (child.type === 'if') continue;
+ if (child.type === 'else') {
+ seenElse = true;
+ continue;
+ }
+
+ if (!seenElse && !consequent) {
+ consequent = child;
+ } else if (seenElse && !alternate) {
+ alternate = child;
+ }
+ }
+
+ if (!consequent || !alternate) {
+ ctx.errors.push(
+ `${locationStr(node)}: could not parse conditional expression`
+ );
+ return null;
+ }
+
+ // Recursively resolve both branches (handles nested ternaries)
+ const consequentNode = await resolveStaticValue(consequent, ctx);
+ const alternateNode = await resolveStaticValue(alternate, ctx);
+
+ if (!consequentNode || !alternateNode) return null;
+
+ // Flatten choices
+ const branches: StringNode[] = [];
+ if (consequentNode.type === 'choice') {
+ branches.push(...consequentNode.nodes);
+ } else {
+ branches.push(consequentNode);
+ }
+ if (alternateNode.type === 'choice') {
+ branches.push(...alternateNode.nodes);
+ } else {
+ branches.push(alternateNode);
+ }
+
+ return { type: 'choice', nodes: branches };
+}
+
+/**
+ * Resolves a function call to its string return variants.
+ * Looks up the function locally, then in imported files.
+ */
+async function resolveFunctionCall(
+ callNode: SyntaxNode,
+ ctx: ParseContext
+): Promise {
+ const funcNode = callNode.childForFieldName('function');
+ if (!funcNode || funcNode.type !== 'identifier') {
+ ctx.errors.push(
+ `${locationStr(callNode)}: cannot resolve non-identifier function call`
+ );
+ return null;
+ }
+
+ const funcName = funcNode.text;
+
+ // Expression parser callback for resolving return expressions.
+ // Receives the actual rootNode and filePath from whichever file
+ // the function is defined in (handles re-exports correctly).
+ const exprParser = (
+ node: SyntaxNode,
+ targetRootNode: SyntaxNode,
+ targetFilePath: string
+ ) => {
+ const targetImports = extractImportsFromRoot(targetRootNode, ctx.imports);
+ return resolveStaticValue(node, {
+ rootNode: targetRootNode,
+ imports: targetImports,
+ filePath: targetFilePath,
+ errors: ctx.errors,
+ });
+ };
+
+ // Try resolving in current file
+ const localResult = await resolveFunctionInCurrentFile(
+ funcName,
+ ctx.rootNode,
+ ctx.filePath,
+ exprParser
+ );
+ if (localResult) return localResult;
+
+ // Try resolving from imports (follows re-export chains automatically)
+ const importInfo = findImportForName(funcName, ctx);
+ if (importInfo) {
+ const result = await resolveFunctionInFile(
+ importInfo.originalName,
+ importInfo.filePath,
+ exprParser
+ );
+ if (result) return result;
+ }
+
+ ctx.errors.push(
+ `${locationStr(callNode)}: could not resolve function "${funcName}" to string return values`
+ );
+ return null;
+}
+
+/**
+ * Extracts GT import aliases from a target file's root node.
+ * Merges with parent imports for GT package functions (declare_var, etc.)
+ * that may not be imported in the target file.
+ */
+function extractImportsFromRoot(
+ rootNode: SyntaxNode,
+ parentImports: ImportAlias[]
+): ImportAlias[] {
+ // Extract GT-only imports from the target file using the same
+ // filtering logic as the main extractImports (filters by GT packages)
+ const fileImports = extractImports(rootNode);
+
+ // Carry over GT declare_* imports from the calling context
+ // (in case the helper file doesn't import them directly)
+ const parentDeclareImports = parentImports.filter(
+ (imp) =>
+ imp.originalName === PYTHON_DECLARE_STATIC ||
+ imp.originalName === PYTHON_DECLARE_VAR
+ );
+
+ // Deduplicate: prefer the target file's own imports over parent's
+ const seen = new Set(fileImports.map((imp) => imp.localName));
+ const merged = [...fileImports];
+ for (const imp of parentDeclareImports) {
+ if (!seen.has(imp.localName)) {
+ merged.push(imp);
+ }
+ }
+
+ return merged;
+}
+
+/**
+ * Resolves the argument of a declare_var() call.
+ * Produces ICU placeholder text using declareVar from generaltranslation.
+ */
+async function resolveDeclareVarArg(
+ callNode: SyntaxNode,
+ ctx: ParseContext
+): Promise {
+ const argsNode = callNode.childForFieldName('arguments');
+ if (!argsNode) {
+ ctx.errors.push(
+ `${locationStr(callNode)}: declare_var() requires arguments`
+ );
+ return null;
+ }
+
+ // Get the first positional arg (the variable - we use empty string since it's runtime)
+ const firstArg = getFirstPositionalArg(callNode);
+ if (!firstArg) {
+ ctx.errors.push(
+ `${locationStr(callNode)}: declare_var() requires a variable argument`
+ );
+ return null;
+ }
+
+ // Extract optional _name kwarg
+ let nameOption: string | undefined;
+ for (let i = 0; i < argsNode.childCount; i++) {
+ const child = argsNode.child(i);
+ if (!child || child.type !== 'keyword_argument') continue;
+
+ const nameNode = child.childForFieldName('name');
+ const valueNode = child.childForFieldName('value');
+ if (!nameNode || !valueNode) continue;
+
+ if (nameNode.text === '_name') {
+ if (valueNode.type === 'string' && !isFString(valueNode)) {
+ nameOption = extractStringContent(valueNode);
+ }
+ }
+ }
+
+ // Use declareVar with empty string for the runtime variable value
+ const options = nameOption ? { $name: nameOption } : undefined;
+ const icuText = declareVar('', options);
+
+ return { type: 'text', text: icuText };
+}
+
+// ===== Helpers ===== //
+
+function getFirstPositionalArg(callNode: SyntaxNode): SyntaxNode | null {
+ const argsNode = callNode.childForFieldName('arguments');
+ if (!argsNode) return null;
+
+ for (let i = 0; i < argsNode.childCount; i++) {
+ const child = argsNode.child(i);
+ if (
+ child &&
+ child.type !== '(' &&
+ child.type !== ')' &&
+ child.type !== ',' &&
+ child.type !== 'keyword_argument'
+ ) {
+ return child;
+ }
+ }
+ return null;
+}
+
+function getOriginalImportName(
+ localName: string,
+ imports: ImportAlias[]
+): string | null {
+ for (const imp of imports) {
+ if (imp.localName === localName) {
+ return imp.originalName;
+ }
+ }
+ return null;
+}
+
+function getStaticImportNames(imports: ImportAlias[]): Set {
+ const names = new Set();
+ for (const imp of imports) {
+ if (
+ imp.originalName === PYTHON_DECLARE_STATIC ||
+ imp.originalName === PYTHON_DECLARE_VAR
+ ) {
+ names.add(imp.localName);
+ }
+ }
+ return names;
+}
+
+/**
+ * Finds import info for a given local name (for cross-file function resolution).
+ * Only looks at non-GT imports (user function imports).
+ */
+function findImportForName(
+ localName: string,
+ ctx: ParseContext
+): { originalName: string; filePath: string } | null {
+ // Walk the AST to find import_from_statement nodes
+ for (let i = 0; i < ctx.rootNode.childCount; i++) {
+ const node = ctx.rootNode.child(i);
+ if (!node || node.type !== 'import_from_statement') continue;
+
+ const moduleName = getModuleName(node);
+ if (!moduleName) continue;
+
+ // Check all imported names in this statement
+ for (let j = 0; j < node.childCount; j++) {
+ const child = node.child(j);
+ if (!child) continue;
+
+ if (child.type === 'aliased_import') {
+ const nameNode = child.childForFieldName('name');
+ const aliasNode = child.childForFieldName('alias');
+ const importedName = nameNode?.text;
+ const alias = aliasNode?.text ?? importedName;
+ if (alias === localName && importedName) {
+ const filePath = resolveImportPath(moduleName, ctx.filePath);
+ if (filePath) {
+ return { originalName: importedName, filePath };
+ }
+ }
+ } else if (child.type === 'dotted_name') {
+ if (child.text === moduleName) continue; // Skip module name itself
+ if (child.text === localName) {
+ const filePath = resolveImportPath(moduleName, ctx.filePath);
+ if (filePath) {
+ return { originalName: localName, filePath };
+ }
+ }
+ }
+ }
+ }
+
+ return null;
+}
+
+function getModuleName(importNode: SyntaxNode): string | undefined {
+ const moduleNode = importNode.childForFieldName('module_name');
+ if (moduleNode) return moduleNode.text;
+
+ for (let i = 0; i < importNode.childCount; i++) {
+ const child = importNode.child(i);
+ if (!child) continue;
+ if (child.type === 'import') break;
+ if (child.type === 'dotted_name') return child.text;
+ if (child.type === 'relative_import') return child.text;
+ }
+ return undefined;
+}
+
+function isFString(stringNode: SyntaxNode): boolean {
+ for (let i = 0; i < stringNode.childCount; i++) {
+ const child = stringNode.child(i);
+ if (child && child.type === 'string_start') {
+ return /^[fF]/.test(child.text);
+ }
+ if (child && child.type === 'interpolation') {
+ return true;
+ }
+ }
+ return false;
+}
+
+function extractStringContent(stringNode: SyntaxNode): string | undefined {
+ for (let i = 0; i < stringNode.childCount; i++) {
+ const child = stringNode.child(i);
+ if (child && child.type === 'string_content') {
+ return child.text;
+ }
+ }
+
+ let hasStart = false;
+ let hasEnd = false;
+ for (let i = 0; i < stringNode.childCount; i++) {
+ const child = stringNode.child(i);
+ if (child?.type === 'string_start') hasStart = true;
+ if (child?.type === 'string_end') hasEnd = true;
+ }
+ if (hasStart && hasEnd) return '';
+
+ return undefined;
+}
+
+function locationStr(node: SyntaxNode): string {
+ return `line ${node.startPosition.row + 1}, col ${node.startPosition.column}`;
+}
diff --git a/packages/python-extractor/src/parser.ts b/packages/python-extractor/src/parser.ts
new file mode 100644
index 000000000..6a1b5f1bf
--- /dev/null
+++ b/packages/python-extractor/src/parser.ts
@@ -0,0 +1,32 @@
+import { Parser, Language } from 'web-tree-sitter';
+import { createRequire } from 'module';
+
+let parserPromise: Promise | null = null;
+
+/**
+ * Lazily initializes and returns a singleton tree-sitter Parser
+ * configured for Python.
+ */
+export function getParser(): Promise {
+ if (!parserPromise) {
+ parserPromise = initParser();
+ }
+ return parserPromise;
+}
+
+async function initParser(): Promise {
+ await Parser.init();
+ const parser = new Parser();
+
+ const require = createRequire(import.meta.url);
+ const wasmPath = require.resolve(
+ 'tree-sitter-python/tree-sitter-python.wasm'
+ );
+ const Python = await Language.load(wasmPath);
+ parser.setLanguage(Python);
+
+ return parser;
+}
+
+export type { Parser };
+export { type Tree, type Node as SyntaxNode } from 'web-tree-sitter';
diff --git a/packages/python-extractor/src/resolveFunctionVariants.ts b/packages/python-extractor/src/resolveFunctionVariants.ts
new file mode 100644
index 000000000..e2249c609
--- /dev/null
+++ b/packages/python-extractor/src/resolveFunctionVariants.ts
@@ -0,0 +1,261 @@
+import fs from 'node:fs';
+import type { SyntaxNode } from './parser.js';
+import { getParser } from './parser.js';
+import type { StringNode } from './stringNode.js';
+import { resolveImportPath } from './resolveImport.js';
+
+/**
+ * Callback to parse a return expression into a StringNode.
+ * Provided by the caller so function resolution doesn't need to know about
+ * declare_var, declare_static, imports, etc.
+ *
+ * @param node - The return expression AST node
+ * @param rootNode - The root AST node of the file containing the function
+ * @param filePath - The absolute path of the file containing the function
+ */
+export type ExpressionParser = (
+ node: SyntaxNode,
+ rootNode: SyntaxNode,
+ filePath: string
+) => Promise;
+
+const crossFileCache = new Map();
+
+/**
+ * Resolves all return values of a function defined in the current file's AST.
+ * Uses the provided expression parser to handle complex return expressions
+ * (concat, declare_var, etc.).
+ */
+export async function resolveFunctionInCurrentFile(
+ functionName: string,
+ rootNode: SyntaxNode,
+ filePath: string,
+ parseExpr: ExpressionParser
+): Promise {
+ const funcDef = findFunctionDefinition(rootNode, functionName);
+ if (!funcDef) return null;
+ return extractReturnVariants(funcDef, rootNode, filePath, parseExpr);
+}
+
+/**
+ * Resolves all return values of a function defined in an external file.
+ * Follows re-export chains: if the function isn't defined in the target file
+ * but is imported from another module, follows the import to the source.
+ * Results are cached by filePath::functionName.
+ */
+export async function resolveFunctionInFile(
+ functionName: string,
+ filePath: string,
+ parseExpr: ExpressionParser,
+ visited?: Set
+): Promise {
+ const cacheKey = `${filePath}::${functionName}`;
+ if (crossFileCache.has(cacheKey)) {
+ return crossFileCache.get(cacheKey)!;
+ }
+
+ // Prevent infinite re-export loops
+ const visitedSet = visited ?? new Set();
+ if (visitedSet.has(cacheKey)) {
+ crossFileCache.set(cacheKey, null);
+ return null;
+ }
+ visitedSet.add(cacheKey);
+
+ let source: string;
+ try {
+ source = fs.readFileSync(filePath, 'utf8');
+ } catch {
+ crossFileCache.set(cacheKey, null);
+ return null;
+ }
+
+ const parser = await getParser();
+ const tree = parser.parse(source);
+ if (!tree) {
+ crossFileCache.set(cacheKey, null);
+ return null;
+ }
+
+ // Try to find function definition in this file
+ const result = await resolveFunctionInCurrentFile(
+ functionName,
+ tree.rootNode,
+ filePath,
+ parseExpr
+ );
+ if (result) {
+ crossFileCache.set(cacheKey, result);
+ return result;
+ }
+
+ // Function not defined here — check for re-exports
+ const reExportInfo = findReExport(functionName, tree.rootNode, filePath);
+ if (reExportInfo) {
+ const reResult = await resolveFunctionInFile(
+ reExportInfo.originalName,
+ reExportInfo.filePath,
+ parseExpr,
+ visitedSet
+ );
+ crossFileCache.set(cacheKey, reResult);
+ return reResult;
+ }
+
+ crossFileCache.set(cacheKey, null);
+ return null;
+}
+
+/**
+ * Finds a top-level function_definition by name in the AST.
+ */
+function findFunctionDefinition(
+ rootNode: SyntaxNode,
+ name: string
+): SyntaxNode | null {
+ for (let i = 0; i < rootNode.childCount; i++) {
+ const child = rootNode.child(i);
+ if (!child) continue;
+
+ if (child.type === 'function_definition') {
+ const nameNode = child.childForFieldName('name');
+ if (nameNode && nameNode.text === name) {
+ return child;
+ }
+ }
+
+ // Also check decorated definitions
+ if (child.type === 'decorated_definition') {
+ const defNode = child.childForFieldName('definition');
+ if (defNode && defNode.type === 'function_definition') {
+ const nameNode = defNode.childForFieldName('name');
+ if (nameNode && nameNode.text === name) {
+ return defNode;
+ }
+ }
+ }
+ }
+ return null;
+}
+
+/**
+ * Extracts all return values from a function body and parses them into StringNodes.
+ * Skips nested function definitions.
+ */
+async function extractReturnVariants(
+ funcDef: SyntaxNode,
+ rootNode: SyntaxNode,
+ filePath: string,
+ parseExpr: ExpressionParser
+): Promise {
+ const body = funcDef.childForFieldName('body');
+ if (!body) return null;
+
+ const returnExprs: SyntaxNode[] = [];
+ collectReturnExpressions(body, returnExprs);
+
+ if (returnExprs.length === 0) return null;
+
+ // Parse each return expression into a StringNode
+ const nodes: StringNode[] = [];
+ for (const expr of returnExprs) {
+ const node = await parseExpr(expr, rootNode, filePath);
+ if (node) nodes.push(node);
+ }
+
+ if (nodes.length === 0) return null;
+ if (nodes.length === 1) return nodes[0];
+
+ return { type: 'choice', nodes };
+}
+
+/**
+ * Recursively collects return expression nodes from a function body,
+ * skipping nested function definitions.
+ */
+function collectReturnExpressions(
+ node: SyntaxNode,
+ results: SyntaxNode[]
+): void {
+ if (node.type === 'function_definition') {
+ // Skip nested function bodies
+ return;
+ }
+
+ if (node.type === 'return_statement') {
+ // Get the expression being returned (skip the 'return' keyword)
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (!child || child.type === 'return') continue;
+ results.push(child);
+ break; // Only one expression per return
+ }
+ return;
+ }
+
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child) collectReturnExpressions(child, results);
+ }
+}
+
+/**
+ * Checks if a function name is re-exported from another module in the given file.
+ * e.g., `from static_test import get_gender` makes `get_gender` a re-export.
+ */
+function findReExport(
+ functionName: string,
+ rootNode: SyntaxNode,
+ currentFilePath: string
+): { originalName: string; filePath: string } | null {
+ for (let i = 0; i < rootNode.childCount; i++) {
+ const node = rootNode.child(i);
+ if (!node || node.type !== 'import_from_statement') continue;
+
+ const moduleName = getModuleName(node);
+ if (!moduleName) continue;
+
+ for (let j = 0; j < node.childCount; j++) {
+ const child = node.child(j);
+ if (!child) continue;
+
+ if (child.type === 'dotted_name' && child.text !== moduleName) {
+ if (child.text === functionName) {
+ const resolved = resolveImportPath(moduleName, currentFilePath);
+ if (resolved) {
+ return { originalName: functionName, filePath: resolved };
+ }
+ }
+ } else if (child.type === 'aliased_import') {
+ const nameNode = child.childForFieldName('name');
+ const aliasNode = child.childForFieldName('alias');
+ const alias = aliasNode?.text ?? nameNode?.text;
+ if (alias === functionName && nameNode) {
+ const resolved = resolveImportPath(moduleName, currentFilePath);
+ if (resolved) {
+ return { originalName: nameNode.text, filePath: resolved };
+ }
+ }
+ }
+ }
+ }
+ return null;
+}
+
+function getModuleName(importNode: SyntaxNode): string | undefined {
+ const moduleNode = importNode.childForFieldName('module_name');
+ if (moduleNode) return moduleNode.text;
+
+ for (let i = 0; i < importNode.childCount; i++) {
+ const child = importNode.child(i);
+ if (!child) continue;
+ if (child.type === 'import') break;
+ if (child.type === 'dotted_name') return child.text;
+ if (child.type === 'relative_import') return child.text;
+ }
+ return undefined;
+}
+
+export function clearFunctionCache(): void {
+ crossFileCache.clear();
+}
diff --git a/packages/python-extractor/src/resolveImport.ts b/packages/python-extractor/src/resolveImport.ts
new file mode 100644
index 000000000..725b28982
--- /dev/null
+++ b/packages/python-extractor/src/resolveImport.ts
@@ -0,0 +1,84 @@
+import fs from 'node:fs';
+import path from 'node:path';
+
+const resolveCache = new Map();
+
+/**
+ * Resolves a Python import module path to a file path on disk.
+ *
+ * Handles:
+ * - Relative imports: `.helpers` → sibling `helpers.py`
+ * - Parent relative imports: `..utils` → parent dir `utils.py`
+ * - Absolute imports: `utils` → `utils.py` in same/parent dirs
+ * - Dotted paths: `myapp.utils` → `myapp/utils.py`
+ */
+export function resolveImportPath(
+ moduleName: string,
+ currentFilePath: string
+): string | null {
+ const cacheKey = `${currentFilePath}::${moduleName}`;
+ if (resolveCache.has(cacheKey)) {
+ return resolveCache.get(cacheKey)!;
+ }
+
+ const result = doResolve(moduleName, currentFilePath);
+ resolveCache.set(cacheKey, result);
+ return result;
+}
+
+function doResolve(moduleName: string, currentFilePath: string): string | null {
+ const currentDir = path.dirname(currentFilePath);
+
+ // Relative import: starts with dots
+ if (moduleName.startsWith('.')) {
+ // Count leading dots
+ let dotCount = 0;
+ while (dotCount < moduleName.length && moduleName[dotCount] === '.') {
+ dotCount++;
+ }
+
+ // Go up (dotCount - 1) directories from current dir
+ let baseDir = currentDir;
+ for (let i = 1; i < dotCount; i++) {
+ baseDir = path.dirname(baseDir);
+ }
+
+ const remainder = moduleName.slice(dotCount);
+ if (!remainder) {
+ // Bare dot import (e.g., "from . import X") — resolve to __init__.py
+ const initPath = path.join(baseDir, '__init__.py');
+ if (fs.existsSync(initPath)) return initPath;
+ return null;
+ }
+
+ return resolveModulePath(baseDir, remainder);
+ }
+
+ // Absolute import: dotted or simple name
+ return resolveModulePath(currentDir, moduleName);
+}
+
+/**
+ * Resolves a dotted module name (e.g. "myapp.utils") relative to a base dir.
+ * Tries: baseDir/myapp/utils.py, then baseDir/myapp/utils/__init__.py
+ */
+function resolveModulePath(baseDir: string, moduleName: string): string | null {
+ const parts = moduleName.split('.');
+ const filePath = path.join(baseDir, ...parts) + '.py';
+
+ if (fs.existsSync(filePath)) {
+ return filePath;
+ }
+
+ // Try as package: moduleName/__init__.py
+ const initPath = path.join(baseDir, ...parts, '__init__.py');
+ if (fs.existsSync(initPath)) {
+ return initPath;
+ }
+
+ return null;
+}
+
+export function clearResolveCache(): void {
+ resolveCache.clear();
+}
diff --git a/packages/python-extractor/src/stringNode.ts b/packages/python-extractor/src/stringNode.ts
new file mode 100644
index 000000000..e8ca97345
--- /dev/null
+++ b/packages/python-extractor/src/stringNode.ts
@@ -0,0 +1,85 @@
+// ===== Tree Construction ===== //
+// Used for declare_static / declare_var parsing
+
+type StringNode = StringTextNode | StringSequenceNode | StringChoiceNode;
+
+type StringTextNode = {
+ type: 'text';
+ text: string;
+};
+
+type StringSequenceNode = {
+ type: 'sequence';
+ nodes: StringNode[];
+};
+
+type StringChoiceNode = {
+ type: 'choice';
+ nodes: StringNode[];
+};
+
+export type {
+ StringNode,
+ StringTextNode,
+ StringSequenceNode,
+ StringChoiceNode,
+};
+
+/**
+ * Converts a StringNode tree into all possible string variants.
+ * - TextNode → single string
+ * - SequenceNode → cartesian product of all parts
+ * - ChoiceNode → flattened branches (deduplicated)
+ */
+export function nodeToStrings(node: StringNode | null): string[] {
+ if (node === null) {
+ return [];
+ }
+
+ if (node.type === 'text') {
+ return [node.text];
+ }
+
+ if (node.type === 'sequence') {
+ const partResults: string[][] = node.nodes.map((n) => nodeToStrings(n));
+ return cartesianProduct(partResults);
+ }
+
+ if (node.type === 'choice') {
+ const allStrings: string[] = [];
+ for (const branch of node.nodes) {
+ allStrings.push(...nodeToStrings(branch));
+ }
+ return [...new Set(allStrings)]; // Deduplicate
+ }
+
+ return [];
+}
+
+/**
+ * Creates cartesian product of string arrays and concatenates them.
+ * @example cartesianProduct([["Hello "], ["day", "night"]]) → ["Hello day", "Hello night"]
+ */
+function cartesianProduct(arrays: string[][]): string[] {
+ if (arrays.length === 0) {
+ return [];
+ }
+
+ if (arrays.length === 1) {
+ return arrays[0];
+ }
+
+ let result = arrays[0];
+
+ for (let i = 1; i < arrays.length; i++) {
+ const newResult: string[] = [];
+ for (const prev of result) {
+ for (const curr of arrays[i]) {
+ newResult.push(prev + curr);
+ }
+ }
+ result = newResult;
+ }
+
+ return result;
+}
diff --git a/packages/python-extractor/src/types.ts b/packages/python-extractor/src/types.ts
new file mode 100644
index 000000000..81e04b9b2
--- /dev/null
+++ b/packages/python-extractor/src/types.ts
@@ -0,0 +1,17 @@
+export type ExtractionResult = {
+ /** The data format of the extracted content */
+ dataFormat: 'ICU' | 'JSX';
+ /** The extracted translatable content */
+ source: string;
+ /** Metadata about the extraction */
+ metadata: ExtractionMetadata;
+};
+
+export type ExtractionMetadata = {
+ id?: string;
+ context?: string;
+ maxChars?: number;
+ filePaths?: string[];
+ /** Groups related static content variants together (for declareStatic/declare_static) */
+ staticId?: string;
+};
diff --git a/packages/python-extractor/tsconfig.json b/packages/python-extractor/tsconfig.json
new file mode 100644
index 000000000..2e586b583
--- /dev/null
+++ b/packages/python-extractor/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "node16",
+ "moduleResolution": "node16",
+ "declaration": true,
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "declarationDir": "./dist",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist", "**/__tests__/**/*"]
+}
diff --git a/packages/python-extractor/vitest.config.ts b/packages/python-extractor/vitest.config.ts
new file mode 100644
index 000000000..75cf66e45
--- /dev/null
+++ b/packages/python-extractor/vitest.config.ts
@@ -0,0 +1,9 @@
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+ test: {
+ globals: true,
+ environment: 'node',
+ include: ['src/**/*.test.ts', 'src/**/*.spec.ts'],
+ },
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7848dc8af..e84e239e3 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -77,10 +77,10 @@ importers:
dependencies:
gt:
specifier: latest
- version: 0.10.0
+ version: 2.7.1(@babel/core@7.28.6)
gt-next:
specifier: latest
- version: 6.12.11(next@16.1.6(@babel/core@7.28.6)(@playwright/test@1.57.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ version: 6.13.5(next@16.1.6(@babel/core@7.28.6)(@playwright/test@1.57.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
next:
specifier: latest
version: 16.1.6(@babel/core@7.28.6)(@playwright/test@1.57.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -136,6 +136,9 @@ importers:
'@formatjs/icu-messageformat-parser':
specifier: ^2.11.4
version: 2.11.4
+ '@generaltranslation/python-extractor':
+ specifier: workspace:*
+ version: link:../python-extractor
chalk:
specifier: ^5.4.1
version: 5.6.2
@@ -202,6 +205,9 @@ importers:
resolve:
specifier: ^1.22.10
version: 1.22.10
+ smol-toml:
+ specifier: ^1.3.1
+ version: 1.6.0
tsconfig-paths:
specifier: ^4.2.0
version: 4.2.0
@@ -661,6 +667,28 @@ importers:
specifier: ^5.6.2
version: 5.9.2
+ packages/python-extractor:
+ dependencies:
+ generaltranslation:
+ specifier: workspace:*
+ version: link:../core
+ tree-sitter-python:
+ specifier: ^0.25.0
+ version: 0.25.0
+ web-tree-sitter:
+ specifier: ^0.26.6
+ version: 0.26.6
+ devDependencies:
+ '@types/node':
+ specifier: ^22.5.1
+ version: 22.13.10
+ typescript:
+ specifier: ^5.5.4
+ version: 5.9.2
+ vitest:
+ specifier: ^2.0.0
+ version: 2.1.9(@edge-runtime/vm@4.0.4)(@types/node@22.13.10)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6))(lightningcss@1.30.1)(terser@5.44.0)
+
packages/react:
dependencies:
'@generaltranslation/react-core':
@@ -2874,17 +2902,17 @@ packages:
'@formatjs/intl-localematcher@0.6.2':
resolution: {integrity: sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==}
- '@generaltranslation/compiler@1.1.18':
- resolution: {integrity: sha512-bF/YH/qubeen8MhllbQG7It66pWoEvByaxTZo5Qc53641QRFDqsX7yFTZ+Sllw5f01mCvyvrgjsG8ulc6PE2NQ==}
+ '@generaltranslation/compiler@1.1.25':
+ resolution: {integrity: sha512-0czukBRnBQn+8ncEEbyyWJvPZ3u6z+CAl3tgt3Xpq8BPWqRxAVynmt99mlJeVhiT1KlDXMb70GJBs/37sJNmTw==}
engines: {node: '>=16.0.0'}
- '@generaltranslation/react-core@1.4.7':
- resolution: {integrity: sha512-OqxJsujf90iQh992XvJAeLtl5Qx1B82Qr+TehaLpwytQgKvBhlt6RW1FxztO43axaLT9q1rr/HYtlqBzjno1ZA==}
+ '@generaltranslation/react-core@1.5.4':
+ resolution: {integrity: sha512-YvIFCr7U+nWIj6tmWHhDa79qvWMFiDWgP2g1WCuz2lkRzYmdfxnaXhtX8t94x2KKmZf8oyYV/8DPdluBw9z1og==}
peerDependencies:
react: '>=16.8.0'
- '@generaltranslation/supported-locales@2.0.40':
- resolution: {integrity: sha512-uwJMtPi9xsgif01fmPE4ofWihF0/uBtu4WLLRfFLcvAH3Sj/e9nk6W/lq4s8ehT98H4ZLt09eA133T//X9hSHA==}
+ '@generaltranslation/supported-locales@2.0.47':
+ resolution: {integrity: sha512-seu1X0UgJbA2y9z6Ql7nIoqowj0UoGUDhEXF3ax2dVvnsXsauSdg/Xu325eNhDuCnQPQmE7lat4wvzmgHqi7YA==}
'@humanfs/core@0.19.1':
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
@@ -5244,9 +5272,6 @@ packages:
'@types/json5@0.0.29':
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
- '@types/keyv@3.1.4':
- resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
-
'@types/lodash.merge@4.6.9':
resolution: {integrity: sha512-23sHDPmzd59kUgWyKGiOMO2Qb9YtqRO/x4IhkgNUiPQ1+5MUVqi6bCZeq9nBJ17msjIMbEIO5u+XW4Kz6aGUhQ==}
@@ -5319,9 +5344,6 @@ packages:
'@types/resolve@1.20.6':
resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==}
- '@types/responselike@1.0.3':
- resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==}
-
'@types/shallow-equals@1.0.3':
resolution: {integrity: sha512-xZx/hZsf1p9J5lGN/nGTsuW/chJCdlyGxilwg1TS78rygBCU5bpY50zZiFcIimlnl0p41kAyaASsy0bqU7WyBA==}
@@ -5758,17 +5780,10 @@ packages:
xstate:
optional: true
- abbrev@1.0.9:
- resolution: {integrity: sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==}
-
abort-controller@3.0.0:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
engines: {node: '>=6.5'}
- accepts@1.1.4:
- resolution: {integrity: sha512-8EKM6XlFgqSpDcxkT9yxCT8nDSWEVBD0UjgUWMCWh5kH9VU+ar2MhmDDYGxohXujPU8PPz88ukpkvfXFVWygHw==}
- engines: {node: '>= 0.8'}
-
accepts@2.0.0:
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
engines: {node: '>= 0.6'}
@@ -5828,18 +5843,6 @@ packages:
ajv@8.13.0:
resolution: {integrity: sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==}
- allong.es@0.14.0:
- resolution: {integrity: sha512-aY7NxEyGrg3laH+QhrOjr2M8bdul+fpEpYC6oHoiTJQuJxh2M+MbJE1vlF9saf9/8v+z6JmpbOaITAtU4V8hmw==}
- engines: {node: ''}
-
- amdefine@0.1.1:
- resolution: {integrity: sha512-cE/769sItEDt5sSdqmrWMsat+XaA5FJiEou+ZwlY7ef/Jf/517k6nYyUIRPR2o/QbpBg4FiYXj9GyRGNg5f/bg==}
- engines: {node: '>=0.4.2'}
-
- amdefine@1.0.0:
- resolution: {integrity: sha512-AhIk36FIyH9lsHdzXxwo/aWm1QQIChd1cewUHtC2E4hczLKgT/WpVJMZGxKwkvW07p1Bn2gxwRFPeeguHocrQA==}
- engines: {node: '>=0.4.2'}
-
ansi-colors@4.1.3:
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
engines: {node: '>=6'}
@@ -5848,14 +5851,6 @@ packages:
resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
engines: {node: '>=8'}
- ansi-regex@1.1.1:
- resolution: {integrity: sha512-q5i8bFLg2wDfsuR56c1NzlJFPzVD+9mxhDrhqOGigEFa87OZHlF+9dWeGWzVTP/0ECiA/JUGzfzRr2t3eYORRw==}
- engines: {node: '>=0.10.0'}
-
- ansi-regex@2.1.1:
- resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==}
- engines: {node: '>=0.10.0'}
-
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
@@ -5864,10 +5859,6 @@ packages:
resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
engines: {node: '>=12'}
- ansi-styles@2.2.1:
- resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==}
- engines: {node: '>=0.10.0'}
-
ansi-styles@3.2.1:
resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
engines: {node: '>=4'}
@@ -5895,9 +5886,6 @@ packages:
resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==}
engines: {node: '>=14'}
- any-promise@1.3.0:
- resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
-
anymatch@3.1.3:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'}
@@ -5974,9 +5962,6 @@ packages:
resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==}
engines: {node: '>=8'}
- asap@2.0.6:
- resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
-
assertion-error@1.1.0:
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
@@ -6005,18 +5990,6 @@ packages:
async-mutex@0.5.0:
resolution: {integrity: sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==}
- async@0.2.10:
- resolution: {integrity: sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==}
-
- async@0.9.2:
- resolution: {integrity: sha512-l6ToIJIotphWahxxHyzK9bnLR6kM4jJIIgLShZeqLY7iboHoGkdgFl7W2/Ivi4SkMJYGKqW8vSuk0uKUj6qsSw==}
-
- async@1.3.0:
- resolution: {integrity: sha512-JsebKhdkyE+QlWiFF+Mo9n2YBiFXkUNNLN8eLJqowTxTiFV70hDdgldy8Y+muTuOVeGNyOFawqR2zLqPquLyOg==}
-
- async@1.5.0:
- resolution: {integrity: sha512-m9nMwCtLtz29LszVaR0q/FqsJWkrxVoQL95p7JU0us7qUx4WEcySQgwvuneYSGVyvirl81gz7agflS3V1yW14g==}
-
async@3.2.6:
resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
@@ -6084,25 +6057,10 @@ packages:
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
- base64-url@1.2.1:
- resolution: {integrity: sha512-V8E0l1jyyeSSS9R+J9oljx5eq2rqzClInuwaPcyuv0Mm3ViI/3/rcc4rCEO8i4eQ4I0O0FAGYDA2i5xWHHPhzg==}
-
- base64-url@1.3.3:
- resolution: {integrity: sha512-UiVPRwO/m133KIQrOEIqO07D8jaYjFIx7/lYRWTRVR23tDSn00Ves6A+Bk0eLmhyz6IJGSFlNCKUuUBO2ssytA==}
-
baseline-browser-mapping@2.8.9:
resolution: {integrity: sha512-hY/u2lxLrbecMEWSB0IpGzGyDyeoMFQhCvZd2jGFSE5I17Fh01sYUBPCJtkWERw7zrac9+cIghxm/ytJa2X8iA==}
hasBin: true
- basic-auth-connect@1.0.0:
- resolution: {integrity: sha512-kiV+/DTgVro4aZifY/hwRwALBISViL5NP4aReaR2EVJEObpbUBHIkdJh/YpcoEiYt7nBodZ6U2ajZeZvSxUCCg==}
-
- basic-auth@1.0.0:
- resolution: {integrity: sha512-qzxS7/bW/LSiKZzdZw3isPjiVmzXbJLM3ImZZ62WMR3oJQAyqy094Nnb0TA2ZZm65xB7nu0acfTQ99z7wwCDCw==}
-
- batch@0.5.1:
- resolution: {integrity: sha512-OXRjc65VJvFtb7JD5HszSI1WWwsI6YnJS7Qmlx1CaDQrZ5urNIeRjtTyBe1YapNXyoWzrcc4yqg4rNe8YMyong==}
-
before-after-hook@2.2.3:
resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==}
@@ -6123,10 +6081,6 @@ packages:
bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
- body-parser@1.9.3:
- resolution: {integrity: sha512-nVSZlzCeMgePYRfXhLbmkzP9NDTbLwNnMtSD82hx97swlLWZeD7Aw30ffhyET2sEGn6+mn0uWcUHiBOcVF1VOQ==}
- engines: {node: '>= 0.8'}
-
body-parser@2.2.0:
resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==}
engines: {node: '>=18'}
@@ -6180,9 +6134,6 @@ packages:
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
engines: {node: '>=10.16.0'}
- bytes@1.0.0:
- resolution: {integrity: sha512-/x68VkHLeTl3/Ll8IvxdwzhrT+IyKc52e/oyHhA2RwqPqswSnjVbSddfPRwAsJtbilMAPSRWwAlpxdYsSWOTKQ==}
-
bytes@3.0.0:
resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==}
engines: {node: '>= 0.8'}
@@ -6251,10 +6202,6 @@ packages:
resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
engines: {node: '>=18'}
- chalk@1.1.3:
- resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==}
- engines: {node: '>=0.10.0'}
-
chalk@2.4.2:
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
engines: {node: '>=4'}
@@ -6301,12 +6248,6 @@ packages:
resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
engines: {node: '>= 16'}
- check-types@0.6.5:
- resolution: {integrity: sha512-VAGyaArzXtnxRMwxid/IIQFOMwhQfYiCTSMr05jPMYogztU5bc23E66HB53uCjQfEtKNYY2zR33tGMXiXyrTjw==}
-
- check-types@1.4.0:
- resolution: {integrity: sha512-DoKzVQCvkW1v/wTXGayuFu9qX+wcOENt+FMgO2mhSrTnWHNimyK6PeN/83oyfwo6hvrhSUn10Dn3XT6mWtHsKg==}
-
cheerio-select@2.1.0:
resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==}
@@ -6347,12 +6288,6 @@ packages:
resolution: {integrity: sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==}
engines: {node: '>=10'}
- cli-color@1.0.0:
- resolution: {integrity: sha512-uyCD/0Ib+RMeG2lFY8hzIYzZ10vIBAfUCL9FEkZ1nAgr9hQq3t0tR1LDo1dGVqPAzdyDOC9pVImJiM8XQIhliA==}
-
- cli-color@1.1.0:
- resolution: {integrity: sha512-SzsTUTopL62kJOMbLqBUkaLVbkyw0qKB3uMRFxgy9LrEQ5tdFO9dT8oUhqszpJB9FMpVTIQnZMjb6zn0abilvQ==}
-
cli-cursor@3.1.0:
resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
engines: {node: '>=8'}
@@ -6369,10 +6304,6 @@ packages:
resolution: {integrity: sha512-/+40ljC3ONVnYIttjMWrlL51nItDAbBrq2upN8BPyvGU/2n5Oxw3tbNwORCaNuNqLJnxGqOfjUuhsv7l5Q4IsQ==}
engines: {node: '>=18.20'}
- cli-table@0.2.0:
- resolution: {integrity: sha512-AB1fVExK7r4Lfk61XOZuZc83tvDxWsnT9KsY3j9gYsaWCOVc7bCajJlA7S/lVSqC7MX34afRpnfeYoLZ3O4PyQ==}
- engines: {node: '>= 0.2.0'}
-
cli-width@3.0.0:
resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==}
engines: {node: '>= 10'}
@@ -6409,12 +6340,6 @@ packages:
react: ^18 || ^19 || ^19.0.0-rc
react-dom: ^18 || ^19 || ^19.0.0-rc
- coffee-script@1.6.3:
- resolution: {integrity: sha512-lpusDYw9xym9ZOqFIeQuzzvTvunm2nlRL++BYhUcLn+77vuidExZG+qDPSKUfDXvuaHeFK6QavntXF+HiOq+/Q==}
- engines: {node: '>=0.8.0'}
- deprecated: CoffeeScript on NPM has moved to "coffeescript" (no hyphen)
- hasBin: true
-
color-convert@1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
@@ -6441,17 +6366,6 @@ packages:
colord@2.9.3:
resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==}
- colors@0.3.0:
- resolution: {integrity: sha512-zRIkNRjxdyFV2Vuq0Bh8hL/rWgQsBM19aB6Uq9CMot2olUuD1DEPon9SB3GZNDrfOojb6a74AQhSM5BKrAr9tA==}
-
- colors@0.6.0-1:
- resolution: {integrity: sha512-ZaQtySU44lmZRP6M+CovFWnu7QnxLTsr/3wURb7BCOV1/gKjUb/3uu3NsLR+fvA2Jfs6sNfwcVq0Tp2mWYbuxg==}
- engines: {node: '>=0.1.90'}
-
- colors@0.6.2:
- resolution: {integrity: sha512-OsSVtHK8Ir8r3+Fxw/b4jS1ZLPXkV6ZxDRJQzeD7qo0SqMXWrHDM71DgYzPMHY8SFJ0Ao+nNU2p1MmwdzKqPrw==}
- engines: {node: '>=0.1.90'}
-
combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
@@ -6462,10 +6376,6 @@ packages:
comma-separated-tokens@2.0.3:
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
- commander@1.1.1:
- resolution: {integrity: sha512-71Rod2AhcH3JhkBikVpNd0pA+fWsmAaVoti6OR38T76chA7vE3pSerS0Jor4wDw+tOueD2zLVvFOw5H0Rcj7rA==}
- engines: {node: '>= 0.6.x'}
-
commander@12.1.0:
resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==}
engines: {node: '>=18'}
@@ -6488,14 +6398,6 @@ packages:
resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==}
engines: {node: '>= 14'}
- compressible@2.0.18:
- resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==}
- engines: {node: '>= 0.6'}
-
- compression@1.2.2:
- resolution: {integrity: sha512-j91tUroerw0zm2vITqL/XbIk2VZV/Gs5qQ5oVhJBArFSgvO5GSUG12D8omWihIUnQKWmTJ7koBWziOooIG62vw==}
- engines: {node: '>= 0.8.0'}
-
compute-scroll-into-view@3.1.1:
resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==}
@@ -6525,30 +6427,13 @@ packages:
config-chain@1.1.13:
resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==}
- configstore@1.4.0:
- resolution: {integrity: sha512-Zcx2SVdZC06IuRHd2MhkVYFNJBkZBj166LGdsJXRcqNC8Gs5Bwh8mosStNeCBBmtIm4wNii2uarD50qztjKOjw==}
- engines: {node: '>=0.10.0'}
-
configstore@5.0.1:
resolution: {integrity: sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==}
engines: {node: '>=8'}
- connect-timeout@1.4.0:
- resolution: {integrity: sha512-rDzJEQjfyTKQExnmHgZ9d+rzQmo+1mGUVHX/cO6kISzDTKSC88W7Pqc2TH9aUk+IGD9RuNp3HSmCL0DOqPgTlQ==}
- engines: {node: '>= 0.8'}
-
- connect@2.27.6:
- resolution: {integrity: sha512-R/HUyt4kO1yxACdY+U1EwCNZCphSI1JyYJ29ornzQfpnwLdB7Gc7a9NRE3OFgWkS9/i4GFIH4BICuIBvJNe/Pw==}
- engines: {node: '>= 0.8.0'}
- deprecated: connect 2.x series is deprecated
-
console-table-printer@2.14.6:
resolution: {integrity: sha512-MCBl5HNVaFuuHW6FGbL/4fB7N/ormCy+tQ+sxTrF6QtSbSNETvPuOVbkJBhzDgYhvjWGrTma4eYJa37ZuoQsPw==}
- console.json@0.2.1:
- resolution: {integrity: sha512-5x69w0wTFJB1ld0clOONlVQaR/WitljrQwuNWqNjJ3B6GXecK86I7wnqs81HzqLbu68TyjIXkgcbs32F36t3mQ==}
- engines: {node: '> 0.10.*'}
-
content-disposition@0.5.2:
resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==}
engines: {node: '>= 0.6'}
@@ -6567,26 +6452,10 @@ packages:
cookie-es@2.0.0:
resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==}
- cookie-parser@1.3.5:
- resolution: {integrity: sha512-YN/8nzPcK5o6Op4MIzAd4H4qUal5+3UaMhVIeaafFYL0pKvBQA/9Yhzo7ZwvBpjdGshsiTAb1+FC37M6RdPDFg==}
- engines: {node: '>= 0.8.0'}
-
- cookie-signature@1.0.5:
- resolution: {integrity: sha512-Ym05XFKVD+EOB43QU3ovI/KvqFo5MP4BGsH+SkAMn2mdjLj2W4bOSyNsw1Ik1gI7CyDtR9Ae2TUFHexgaiEuZg==}
-
- cookie-signature@1.0.6:
- resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
-
cookie-signature@1.2.2:
resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==}
engines: {node: '>=6.6.0'}
- cookie@0.1.2:
- resolution: {integrity: sha512-+mHmWbhevLwkiBf7QcbZXHr0v4ZQQ/OgHk3fsQHrsMMiGzuvAmU/YMUR+ZfrO/BLAGIWFfx2Z7Oyso0tZR/wiA==}
-
- cookie@0.1.3:
- resolution: {integrity: sha512-mWkFhcL+HVG1KjeCjEBVJJ7s4sAGMLiBDFSDs4bzzvgLZt7rW8BhP6XV/8b1+pNvx/skd3yYxPuaF3Z6LlQzyw==}
-
cookie@0.7.2:
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
engines: {node: '>= 0.6'}
@@ -6614,9 +6483,6 @@ packages:
resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==}
engines: {node: '>= 14'}
- crc@3.2.1:
- resolution: {integrity: sha512-H21TaZQyic++ilBStWHntVpS2STWO37tzE0w0P5iAY1ntaPVtlZ3E6FcwltyZa6MYrEbKMxjEwXh3fBHlW8Qqw==}
-
create-require@1.1.1:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
@@ -6631,10 +6497,6 @@ packages:
resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==}
engines: {node: '>=8'}
- csrf@2.0.7:
- resolution: {integrity: sha512-AA22oKr4/2k9QxCLXOS/10NcGudOsxj6oRTa6HjW3spZbJJgKaODdOo1AnZSuz1fSdVNmf4ddXPLsBX+PDeLNA==}
- engines: {node: '>= 0.8'}
-
css-color-keywords@1.0.0:
resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==}
engines: {node: '>=4'}
@@ -6711,32 +6573,12 @@ packages:
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
- csurf@1.6.6:
- resolution: {integrity: sha512-dDO66hRf7u8nsgFJVQ/HpOOR7WoqtLlsToEzp+k+9mjNssSCFWPN/lAcYP4vtfu9KrADxY727zrPVruqX9lNuQ==}
- engines: {node: '>= 0.8.0'}
- deprecated: This package is archived and no longer maintained. For support, visit https://github.com/expressjs/express/discussions
-
- custom-logger@0.2.1:
- resolution: {integrity: sha512-u9a0nor71UAHu86NRANiZkv0hJkjSr+LHtyzuh2KP7NTnaIp7NtgkQpgO78QB69BuUtl0zeJOEaJZdoUJatz6A==}
- engines: {node: '>=0.1.90'}
-
- custom-logger@0.3.1:
- resolution: {integrity: sha512-uJx5C55CigR3KpNoP7OyBkhiwq+dqH5eujoznW9hsEjKspN88E8m5PukPRDJavfPCQBFZc/IdaQzkM3SC3jGtQ==}
- engines: {node: '>=0.1.90'}
-
custom-media-element@1.4.5:
resolution: {integrity: sha512-cjrsQufETwxjvwZbYbKBCJNvmQ2++G9AvT45zDi7NXL9k2PdVcs2h0jQz96J6G4TMKRCcEsoJ+QTgQD00Igtjw==}
cyclist@1.0.2:
resolution: {integrity: sha512-0sVXIohTfLqVIW3kb/0n6IiWF3Ifj5nm2XaSrLq2DI6fKIGa2fYAZdk917rUneaeLVpYfFcyXE2ft0fe3remsA==}
- d@0.1.1:
- resolution: {integrity: sha512-0SdM9V9pd/OXJHoWmTfNPTAeD+lw6ZqHg+isPyBFuJsZLSE0Ygg1cYZ/0l6DrKQXMOqGOu1oWupMoOfoRfMZrQ==}
-
- d@1.0.2:
- resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==}
- engines: {node: '>=0.12'}
-
damerau-levenshtein@1.0.8:
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
@@ -6776,20 +6618,9 @@ packages:
date-fns@4.1.0:
resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
- dateformat@1.0.2-1.2.3:
- resolution: {integrity: sha512-AXvW8g7tO4ilk5HgOWeDmPi/ZPaCnMJ+9Cg1I3p19w6mcvAAXBuuGEXAxybC+Djj1PSZUiHUcyoYu7WneCX8gQ==}
-
debounce@1.2.1:
resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==}
- debug@2.1.3:
- resolution: {integrity: sha512-KWau3VQmxO3YwQCjJzMPPusOtI0hx3UGsqnY7RS+QHQjUeawpOVtJvAdeTrI2Ja5DTR8KH3xaEN8c+ADbXJWeg==}
- peerDependencies:
- supports-color: '*'
- peerDependenciesMeta:
- supports-color:
- optional: true
-
debug@2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
peerDependencies:
@@ -6894,10 +6725,6 @@ packages:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
- depd@1.0.1:
- resolution: {integrity: sha512-OEWAMbCkK9IWQ8pfTvHBhCSqHgR+sk5pbiYqq0FqfARG4Cy+cRsCbITx6wh5pcsmfBPiJAcbd98tfdz5fnBbag==}
- engines: {node: '>= 0.6'}
-
depd@2.0.0:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'}
@@ -6909,9 +6736,6 @@ packages:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
- destroy@1.0.3:
- resolution: {integrity: sha512-KB/AVLKRwZPOEo6/lxkDJ+Bv3jFRRrhmnRMPvpWwmIfUggpzGkQBqolyo8FRf833b/F5rzmy1uVN3fHBkjTxgw==}
-
detect-indent@6.1.0:
resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
engines: {node: '>=8'}
@@ -7029,9 +6853,6 @@ packages:
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
- ee-first@1.1.0:
- resolution: {integrity: sha512-n4X/DaHVKHyDy1Rwuzm1UPjTRIBSarj1BBZ5R5HLOFLn58yhw510qoF1zk94jjkw3mXScdsmMtYCNR1jsAJlEA==}
-
ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
@@ -7089,10 +6910,6 @@ packages:
error-ex@1.3.4:
resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==}
- errorhandler@1.2.4:
- resolution: {integrity: sha512-IBrieUE13SQlGSq2+uAQ6de/yxlf86mpstJa7j+jvO81zwgcPOX1C1hy2FQaZrj+p1qcXb5u1XUhEbN3pF897A==}
- engines: {node: '>= 0.8'}
-
es-abstract@1.24.0:
resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==}
engines: {node: '>= 0.4'}
@@ -7128,26 +6945,6 @@ packages:
resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
engines: {node: '>= 0.4'}
- es5-ext@0.10.64:
- resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==}
- engines: {node: '>=0.10'}
-
- es6-iterator@0.1.3:
- resolution: {integrity: sha512-6TOmbFM6OPWkTe+bQ3ZuUkvqcWUjAnYjKUCLdbvRsAUz2Pr+fYIibwNXNkLNtIK9PPFbNMZZddaRNkyJhlGJhA==}
-
- es6-iterator@2.0.3:
- resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==}
-
- es6-symbol@2.0.1:
- resolution: {integrity: sha512-wjobO4zO8726HVU7mI2OA/B6QszqwHJuKab7gKHVx+uRfVVYGcWJkCIFxV2Madqb9/RUSrhJ/r6hPfG7FsWtow==}
-
- es6-symbol@3.1.4:
- resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==}
- engines: {node: '>=0.12'}
-
- es6-weak-map@0.1.4:
- resolution: {integrity: sha512-P+N5Cd2TXeb7G59euFiM7snORspgbInS29Nbf3KNO2JQp/DyhvMCDWd58nsVAXwYJ6W3Bx7qDdy6QQ3PCJ7jKQ==}
-
esbuild-register@3.6.0:
resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==}
peerDependencies:
@@ -7177,9 +6974,6 @@ packages:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
- escape-html@1.0.1:
- resolution: {integrity: sha512-z6kAnok8fqVTra7Yu77dZF2Y6ETJlxH58wN38wNyuNQLm8xXdKnfNrlSmfXsTePWP03rRVUKHubtUwanwUi7+g==}
-
escape-html@1.0.3:
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
@@ -7199,21 +6993,6 @@ packages:
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
engines: {node: '>=12'}
- escodegen@1.3.3:
- resolution: {integrity: sha512-z9FWgKc48wjMlpzF5ymKS1AF8OIgnKLp9VyN7KbdtyrP/9lndwUFqCtMm+TAJmJf7KJFFYc4cFJfVTTGkKEwsA==}
- engines: {node: '>=0.10.0'}
- hasBin: true
-
- escodegen@1.6.1:
- resolution: {integrity: sha512-n+5A2GpcotTT9EGSDrh6Okve4QU1tRp4ktb3CdutEs2e3ZEkY+NImmY+LclV4NVKOITdNsmOldSmqlcd7tlZzg==}
- engines: {node: '>=0.10.0'}
- hasBin: true
-
- escodegen@1.7.1:
- resolution: {integrity: sha512-2cd7+JUtUEmZVpGmfF9r+uRYXswJAkf85Ce8GvdBa7hSvdjY8hGo+rwC5syAgYzqHpfxNJzLntFjw6879yPbgQ==}
- engines: {node: '>=0.12.0'}
- hasBin: true
-
eslint-config-next@15.5.2:
resolution: {integrity: sha512-3hPZghsLupMxxZ2ggjIIrat/bPniM2yRpsVPVM40rp8ZMzKWOJp2CGWn7+EzoV2ddkUr5fxNfHpF+wU1hGt/3g==}
peerDependencies:
@@ -7386,10 +7165,6 @@ packages:
resolution: {integrity: sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==}
engines: {node: '>=6'}
- esniff@2.0.1:
- resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==}
- engines: {node: '>=0.10'}
-
espree@10.4.0:
resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -7398,26 +7173,6 @@ packages:
resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
- esprima@1.1.1:
- resolution: {integrity: sha512-qxxB994/7NtERxgXdFgLHIs9M6bhLXc6qtUmWZ3L8+gTQ9qaoyki2887P2IqAYsoENyr8SUbTutStDniOHSDHg==}
- engines: {node: '>=0.4.0'}
- hasBin: true
-
- esprima@1.2.5:
- resolution: {integrity: sha512-S9VbPDU0adFErpDai3qDkjq8+G05ONtKzcyNrPKg/ZKa+tf879nX2KexNU95b31UoTJjRLInNBHHHjFPoCd7lQ==}
- engines: {node: '>=0.4.0'}
- hasBin: true
-
- esprima@2.4.1:
- resolution: {integrity: sha512-oQ5niex1XEkpjZhmW1zsozCG515481U0s+A1n6xU9usjkLSy7ZDvfuaAR+CKAKujczvEy7sOPIiX/GO+MZPk8Q==}
- engines: {node: '>=0.4.0'}
- hasBin: true
-
- esprima@2.5.0:
- resolution: {integrity: sha512-uM6hfS0/8ybNIj8SGRMdidPJy5uhWqWN/GIkyqnMAbCSL44yfFGLuBpRRCgOpBXBZt2OymQuM+IfahkqJq3DWw==}
- engines: {node: '>=0.10.0'}
- hasBin: true
-
esprima@4.0.1:
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
engines: {node: '>=4'}
@@ -7431,14 +7186,6 @@ packages:
resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
engines: {node: '>=4.0'}
- estraverse@1.5.1:
- resolution: {integrity: sha512-FpCjJDfmo3vsc/1zKSeqR5k42tcIhxFIlvq+h9j0fO2q/h2uLKyweq7rYJ+0CoVvrGQOxIS5wyBrW/+vF58BUQ==}
- engines: {node: '>=0.4.0'}
-
- estraverse@1.9.3:
- resolution: {integrity: sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA==}
- engines: {node: '>=0.10.0'}
-
estraverse@5.3.0:
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
engines: {node: '>=4.0'}
@@ -7458,22 +7205,10 @@ packages:
estree-walker@3.0.3:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
- esutils@1.0.0:
- resolution: {integrity: sha512-x/iYH53X3quDwfHRz4y8rn4XcEwwCJeWsul9pF1zldMbGtgOtMNBEOuYWwB1EQlK2LRa1fev3YAgym/RElp5Cg==}
- engines: {node: '>=0.10.0'}
-
- esutils@1.1.6:
- resolution: {integrity: sha512-RG1ZkUT7iFJG9LSHr7KDuuMSlujfeTtMNIcInURxKAxhMtwQhI3NrQhz26gZQYlsYZQKzsnwtpKrFKj9K9Qu1A==}
- engines: {node: '>=0.10.0'}
-
esutils@2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
- etag@1.5.1:
- resolution: {integrity: sha512-Y+bhHICnjqZeY4I1kHDwvWTN0VcrI3ucWNbtofd0LLarRKEK8DkAL0uBdl3HCmf1HMjyrmgC/kqj+zXG5mYe7A==}
- engines: {node: '>= 0.6'}
-
etag@1.8.1:
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
engines: {node: '>= 0.6'}
@@ -7482,9 +7217,6 @@ packages:
resolution: {integrity: sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==}
engines: {node: '>= 0.8'}
- event-emitter@0.3.5:
- resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==}
-
event-source-polyfill@1.0.31:
resolution: {integrity: sha512-4IJSItgS/41IxN5UVAVuAyczwZF7ZIEsM1XAoUzIHA6A+xzusEZUutdXz2Nr+MQPLxfTiCvqE79/C8HT8fKFvA==}
@@ -7547,10 +7279,6 @@ packages:
peerDependencies:
express: '>= 4.11'
- express-session@1.9.3:
- resolution: {integrity: sha512-QypUJ7m7Mz2K5DpZmYkE5bCpA/Ia0212ExvxV3ZWjOYh1grnsji3TJgMxOzp2/thONe5BeBCOmJX/VnOMRd4wg==}
- engines: {node: '>= 0.8.0'}
-
express@5.1.0:
resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==}
engines: {node: '>= 18'}
@@ -7558,9 +7286,6 @@ packages:
exsolve@1.0.8:
resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==}
- ext@1.7.0:
- resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==}
-
extend-shallow@2.0.1:
resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
engines: {node: '>=0.10.0'}
@@ -7595,9 +7320,6 @@ packages:
fast-json-stable-stringify@2.1.0:
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
- fast-levenshtein@1.0.7:
- resolution: {integrity: sha512-hYsfI0s4lfQ2rHVFKXwAr/L/ZSbq9TZwgXtZqW7ANcn9o9GKvcbWxOnxx7jykXf/Ezv1V8TvaBEKcGK7DWKX5A==}
-
fast-levenshtein@2.0.6:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
@@ -7638,20 +7360,10 @@ packages:
filelist@1.0.4:
resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==}
- fileset@0.1.8:
- resolution: {integrity: sha512-Gg0/Iy/v4BfdGWZpbpVBPKIYcap7jMn2uT5lcIDZyMFZR35VDojrJnIAwWjCj7ZOqsGp3j+ExWKqnfGrz4q0fg==}
-
- fileset@0.2.1:
- resolution: {integrity: sha512-aK3PFyHSwWsBJCarRxMRIXSGamfroi9ehG8f4e5A2n5nSlEVHe8y44jNTIN4+HdZSpK3FNV0EdihH1iDWTdnGg==}
-
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
- finalhandler@0.3.2:
- resolution: {integrity: sha512-+V+86srh2q2ebx2iD1qG4J5GULZtZLujK+g6g9Rtwe1sKVekOIRyJaWOKyWoiUMLpI30JPlp1C8QH3ucLGmKoA==}
- engines: {node: '>= 0.8'}
-
finalhandler@2.1.0:
resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==}
engines: {node: '>= 0.8'}
@@ -7751,10 +7463,6 @@ packages:
react-dom:
optional: true
- fresh@0.2.4:
- resolution: {integrity: sha512-mnBGgIFRNu54GtbkXy6+QKPYW/b5joAURorA8ELeJc/5BBNph6Go1NmHa9dt08ghFnhGuLenrUmNO8Za1CwEUQ==}
- engines: {node: '>= 0.6'}
-
fresh@2.0.0:
resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
engines: {node: '>= 0.8'}
@@ -7808,8 +7516,8 @@ packages:
functions-have-names@1.2.3:
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
- generaltranslation@8.1.9:
- resolution: {integrity: sha512-1n1CXjY96oB4T88mqtN5AICdiaPhGvnNnxgsWJ98dWOBry6j0rNzNHB0oKslS+ui9GUEPmPcZ1KdLHuv75c7TA==}
+ generaltranslation@8.1.14:
+ resolution: {integrity: sha512-/0GsjmfN/GcbGT9HJ9IurbUYYIQUtsN0zNzP2l3xy9lqha6KE1SzdtuyfBvUZsSI7fSQOYP6HOqUIzQ+p80ILg==}
generic-names@4.0.0:
resolution: {integrity: sha512-ySFolZQfw9FoDb3ed9d80Cm9f0+r7qj+HJkWjeD9RBfpxEVTlVhol+gvaQB/78WbwYfbnNh8nWHHBSlg072y6A==}
@@ -7892,11 +7600,6 @@ packages:
get-uri@2.0.4:
resolution: {integrity: sha512-v7LT/s8kVjs+Tx0ykk1I+H/rbpzkHvuIq87LmeXptcf5sNWm9uQiwjNAt94SJPA1zOlCntmnOlJvVWKmzsxG8Q==}
- ggit@0.0.5:
- resolution: {integrity: sha512-yxmITEEcFhcFXi570J1cOE4uEHMBo8qarfA9WlUYHkcuwtokgpp+SVTx/DeDrCq2JcW4jhRmcz3gytQTPUoJ/g==}
- engines: {node: '>= 0.8.0'}
- hasBin: true
-
git-config-path@1.0.1:
resolution: {integrity: sha512-KcJ2dlrrP5DbBnYIZ2nlikALfRhKzNSX0stvv3ImJ+fvC4hXKoV+U+74SV0upg+jlQZbrtQzc0bu6/Zh+7aQbg==}
engines: {node: '>=0.10.0'}
@@ -7953,21 +7656,9 @@ packages:
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
hasBin: true
- glob@3.2.11:
- resolution: {integrity: sha512-hVb0zwEZwC1FXSKRPFTeOtN7AArJcJlI6ULGLtrstaswKNlrTJqAA+1lYlSUop4vjA423xlBzqfVS3iWGlqJ+g==}
- deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
-
- glob@5.0.13:
- resolution: {integrity: sha512-UUX7KcKGxsailKUG+md76uvcasQ+pwcb5X6o97LcqGobNvcBvXvWPvhAF+FndmzEXBFB9xdT2ME5DkzS8F5zIg==}
- deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
-
- glob@5.0.15:
- resolution: {integrity: sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==}
- deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
-
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
- deprecated: Glob versions prior to v9 are no longer supported
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
glob@9.3.5:
resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==}
@@ -8012,10 +7703,6 @@ packages:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
- got@3.3.1:
- resolution: {integrity: sha512-7chPlc0pWHjvq7B6dEEXz4GphoDupOvBSSl6AwRsAJX7GPTZ+bturaZiIigX4Dp6KrAP67nvzuKkNc0SLA0DKg==}
- engines: {node: '>=0.10.0'}
-
graceful-fs@4.2.10:
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
@@ -8037,30 +7724,27 @@ packages:
resolution: {integrity: sha512-4kGV3sn308DQ6X5vxm1sMhRtnm6r2cXXf/1xOiJkkS3YZ74IXRxl6niGwhhLYRJUCHZ52dEitmlXFYxhqkbiZQ==}
engines: {node: '>=20.19 <22 || >=22.12'}
- gt-i18n@0.3.7:
- resolution: {integrity: sha512-F1S8WMIOWWiQTMsmBjIEz/9uP1oCTnvDzQ5OLmsGHS//KAhlfIvj2QwzNPVX5CQ/E4D7WqeznpgLrQNPyEQgow==}
+ gt-i18n@0.4.2:
+ resolution: {integrity: sha512-25R45ZE8m1MbIzJm4pvIC5paVmw5vYwb8V84lj27493OhZ4lUtfLcW5fEn4hgD7edTy/EDMLnxFco8lt1O5CaQ==}
- gt-next@6.12.11:
- resolution: {integrity: sha512-yBqWkpTFdVnOoYhX8+uXMmT7O4DwxZINf2niJe4k/sJ5tXGg6y1iRKzNHU80QZo1acMrEaxxeRR+ZVaGHuuhvw==}
+ gt-next@6.13.5:
+ resolution: {integrity: sha512-WXqShRvu4TSkqEIW2zmBsJmye1rIlyJR83TrPc+5xD9yaKVeZswlqd6duo5MIvy7GMVpOMnwLbayU6HjqN3/9w==}
peerDependencies:
next: '>=13.0.0 <15.2.1 || >15.2.2'
react: '>=16.8.0 <20.0.0'
react-dom: '>=16.8.0 <20.0.0'
- gt-react@10.10.8:
- resolution: {integrity: sha512-AecxKOfuG+Ln/1kaGI4RzdUaagYl1zQ73bWJKXcuPGqUwNNalZHxEUwi4slpreylRJ78XZRLHU8dO0QLo70GEQ==}
+ gt-react@10.11.4:
+ resolution: {integrity: sha512-9wn0RN7WEb3fZF7gBAuiURX2kbh7zAeW9EPoIpQpCzZ5ly3cvLKyeA5x21aLvJ/hWNhNAmOufCsgL3hb4owIGQ==}
peerDependencies:
react: '>=16.8.0'
react-dom: '>=16.8.0'
- gt@0.10.0:
- resolution: {integrity: sha512-AzW+IcsMicNAR62uWzfJYf1nL4xnVnclwYhyGNWX5eoRMaHftYZzd9mASTWsZEatMuoQE+Syd+MNnwEZMN6tCA==}
- engines: {node: '>= 0.8.0'}
- hasBin: true
+ gt-remark@1.0.5:
+ resolution: {integrity: sha512-ixXv2DSH2Nmq0cyu7z9nL3dNqc3MWjh3vL2a8tSD9NO6B+xiDVRmTGVqyBVOSZmGvzxvenS+2po4QcU2TLOjIw==}
- gt@0.8.49:
- resolution: {integrity: sha512-jOkQWTe+5XWUMyqtqLa/aptgoYQUlonyalOlcATi9/LBjdwOkdkNpbAXeuI89LfT8pM61rhbrwCMaiQBs7qxqQ==}
- engines: {node: '>= 0.8.0'}
+ gt@2.7.1:
+ resolution: {integrity: sha512-QT34u6+clJFaUe4JQHXI6sq2gTt4WwpGr0oY62D4GvZXU8LkRSryySfQmoz+70yOz3UJL1mHpnFBbjqk+AK0SQ==}
hasBin: true
gunzip-maybe@1.4.2:
@@ -8077,37 +7761,14 @@ packages:
crossws:
optional: true
- handlebars@1.3.0:
- resolution: {integrity: sha512-l7sLUTqXCkc1Ypoy8mSOWZFEZJK3VYQYnLfIVTRJeSHdgzt6hXkQ0uPGugydPa99KyyBdsi0J3WvYfm/HX5naQ==}
- engines: {node: '>=0.4.7'}
- hasBin: true
-
- handlebars@3.0.0:
- resolution: {integrity: sha512-Qk1V768egy6xX2dBoaqhE/DmPDDQuC23lPjM+cW7qpZ0vW16+LrKr2ugPpBY80Q/p81aiX6CSf0MvWLmPYursA==}
- engines: {node: '>=0.4.7'}
- hasBin: true
-
- handlebars@4.7.8:
- resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==}
- engines: {node: '>=0.4.7'}
- hasBin: true
-
hard-rejection@2.1.0:
resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==}
engines: {node: '>=6'}
- has-ansi@2.0.0:
- resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==}
- engines: {node: '>=0.10.0'}
-
has-bigints@1.1.0:
resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
engines: {node: '>= 0.4'}
- has-flag@1.0.0:
- resolution: {integrity: sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==}
- engines: {node: '>=0.10.0'}
-
has-flag@3.0.0:
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
engines: {node: '>=4'}
@@ -8196,10 +7857,6 @@ packages:
htmlparser2@10.1.0:
resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==}
- http-errors@1.2.8:
- resolution: {integrity: sha512-zqmsJxOyxtIKiQMXiL3PyIlo5d2PWxBzZWAiR3V6Lo/tWX8/n0xrJ5JIr5r+BLeI3obeqi3hoaZkNBCbCcWPZw==}
- engines: {node: '>= 0.6'}
-
http-errors@2.0.0:
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
engines: {node: '>= 0.8'}
@@ -8234,10 +7891,6 @@ packages:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
engines: {node: '>=0.10.0'}
- iconv-lite@0.4.5:
- resolution: {integrity: sha512-LQ4GtDkFagYaac8u4rE73zWu7h0OUUmR0qVBOgzLyFSoJhoDG2xV9PZJWWyVVcYha/9/RZzQHUinFMbNKiOoAA==}
- engines: {node: '>=0.8.0'}
-
iconv-lite@0.6.3:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'}
@@ -8303,9 +7956,6 @@ packages:
resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
engines: {node: '>=8'}
- infinity-agent@2.0.3:
- resolution: {integrity: sha512-CnfUJe5o2S9aAQWXGMhDZI4UL39MAJV3guOTfHHIdos4tuVHkl1j/J+1XLQn+CLIvqcpgQR/p+xXYXzcrhCe5w==}
-
inflight@1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
@@ -8432,10 +8082,6 @@ packages:
resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==}
engines: {node: '>= 0.4'}
- is-finite@1.1.0:
- resolution: {integrity: sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==}
- engines: {node: '>=0.10.0'}
-
is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
@@ -8488,10 +8134,6 @@ packages:
resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
engines: {node: '>= 0.4'}
- is-npm@1.0.0:
- resolution: {integrity: sha512-9r39FIr3d+KD9SbX0sfMsHzb5PP3uimOiwr3YupUaUFG4W0l1U57Rx3utpttV7qz5U3jmrO5auUa04LU9pyHsg==}
- engines: {node: '>=0.10.0'}
-
is-number-object@1.1.1:
resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==}
engines: {node: '>= 0.4'}
@@ -8530,10 +8172,6 @@ packages:
is-promise@4.0.0:
resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
- is-redirect@1.0.0:
- resolution: {integrity: sha512-cr/SlUEe5zOGmzvj9bUyC4LVvkNVAXu4GytXLNMr1pny+a65MpQ9IJzFHD5vi7FyJgb4qt27+eS3TuQnqB+RQw==}
- engines: {node: '>=0.10.0'}
-
is-reference@1.2.1:
resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==}
@@ -8556,10 +8194,6 @@ packages:
is-ssh@1.4.1:
resolution: {integrity: sha512-JNeu1wQsHjyHgn9NcWTaXq6zWSR6hqE0++zhfZlkFBbScNkyvxCdeV8sRkSBaeLKxmbpR21brail63ACNxJ0Tg==}
- is-stream@1.1.0:
- resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==}
- engines: {node: '>=0.10.0'}
-
is-stream@2.0.1:
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
engines: {node: '>=8'}
@@ -8670,30 +8304,6 @@ packages:
resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==}
engines: {node: '>=8'}
- istanbul@0.3.17:
- resolution: {integrity: sha512-KHW1Decaus4A1gyuvzbJBLa97V/Ci+b7mt5Za+byuNjwsLOaU7ssx3vR6x6yOOQ1ubwVOj5fWWXbcFnw00Z1og==}
- deprecated: |-
- This module is no longer maintained, try this instead:
- npm i nyc
- Visit https://istanbul.js.org/integrations for other alternatives.
- hasBin: true
-
- istanbul@0.3.5:
- resolution: {integrity: sha512-CLDyGr9ENe0M4vSn2WU1HobmujmDzX3+R1kgASpB2o8S17eW8uEW8mF21PHHdZGyIvgSnRmuWUTF6TTuJTLfXA==}
- deprecated: |-
- This module is no longer maintained, try this instead:
- npm i nyc
- Visit https://istanbul.js.org/integrations for other alternatives.
- hasBin: true
-
- istanbul@0.4.0:
- resolution: {integrity: sha512-wEy53kU48MZlAAOuo9VlzOu08CnR44Xt/diX4lHSs18T1ByGvWSBrC3oPHCMPktyDwFbh9VI30q1aShEekuI4Q==}
- deprecated: |-
- This module is no longer maintained, try this instead:
- npm i nyc
- Visit https://istanbul.js.org/integrations for other alternatives.
- hasBin: true
-
iterator.prototype@1.1.5:
resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
engines: {node: '>= 0.4'}
@@ -8860,9 +8470,6 @@ packages:
just-clone@6.2.0:
resolution: {integrity: sha512-1IynUYEc/HAwxhi3WDpIpxJbZpMCvvrrmZVqvj9EhpvbH8lls7HhdhiByjL7DkAaWlLIzpC0Xc/VPvy/UxLNjA==}
- keypress@0.1.0:
- resolution: {integrity: sha512-x0yf9PL/nx9Nw9oLL8ZVErFAk85/lslwEP7Vz7s5SI1ODXZIgit3C5qyWjw4DxOuO/3Hb4866SQh28a1V1d+WA==}
-
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
@@ -8885,24 +8492,6 @@ packages:
resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==}
engines: {node: '>=0.10'}
- lasso-node@0.1.13:
- resolution: {integrity: sha512-fbEe3M6xtHZcxSTTDBp3XpJjxkWVS+2Ip0a/ni3tuZSYbGJcQWeDhT7/SF3fB05IayO8E4OmVVyrqh0vu0Kp+Q==}
- engines: {node: '>= 0.8.0'}
- hasBin: true
-
- latest-version@1.0.1:
- resolution: {integrity: sha512-HERbxp4SBlmI380+eM0B0u4nxjfTaPeydIMzl9+9UQ4nSu3xMWKlX9WoT34e4wy7VWe67c53Nv9qPVjS8fHKgg==}
- engines: {node: '>=0.10.0'}
- hasBin: true
-
- lazy-ass@0.5.8:
- resolution: {integrity: sha512-0DHsDW1cFe+j0vwmT/iWvSob3wk2o8GxJYZZCqnU+JnIyx7A4n9dBPgLaoYz5BHzEPY4ipjQSVz6EJeMu5C4ZA==}
- engines: {node: '> 0.8'}
-
- lazy-ass@1.0.0:
- resolution: {integrity: sha512-ZY6KhBvsujAX+UoipJ85THSU3d8y5Qx+4fwGz7AL9LEOi9Zx4SvwetDRgUFPDhwBmFFF1VQ9YjbIV/RJjpVdVA==}
- engines: {node: '> 0.8'}
-
lazystream@1.0.1:
resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==}
engines: {node: '>= 0.6.3'}
@@ -8965,10 +8554,6 @@ packages:
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
engines: {node: '>=6'}
- levn@0.2.5:
- resolution: {integrity: sha512-mvp+NO++YH0B+e8cC/SvJxk6k5Z9Ngd3iXuz7tmT8vZCyQZj/5SI1GkFOiZGGPkm5wWGI9SUrqiAfPq7BJH+0w==}
- engines: {node: '>= 0.8.0'}
-
levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
@@ -9101,24 +8686,6 @@ packages:
lodash.uniq@4.5.0:
resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==}
- lodash@1.2.1:
- resolution: {integrity: sha512-EKI82Edm8glH3FUu17sQIF+Ly1cW2ROPc0qgf1L4DBUysBlQVL+/b+WtufJw0O8FtMo7Vq9KTrzUboyWSgW/tg==}
- engines: {'0': node, '1': rhino}
-
- lodash@2.2.1:
- resolution: {integrity: sha512-rGaKzxe4Biu8YdCPD/tUkBF4/fnAqgj63A6PeAyQnH/NEKNUHgppGZUgYBYOmAZsBKwwAb343Q1Zew0RDB2jIg==}
- engines: {'0': node, '1': rhino}
-
- lodash@2.4.1:
- resolution: {integrity: sha512-qa6QqjA9jJB4AYw+NpD2GI4dzHL6Mv0hL+By6iIul4Ce0C1refrjZJmcGvWdnLUwl4LIPtvzje3UQfGH+nCEsQ==}
- engines: {'0': node, '1': rhino}
-
- lodash@3.10.0:
- resolution: {integrity: sha512-y4sq/rWWfUsEaOR6VYulMCC6QzL7mqb5wV8R09xGUJZ84UoBkn4/nNqZ04YSqXkEQOQabBRKxk3xgzgig2h75A==}
-
- lodash@3.10.1:
- resolution: {integrity: sha512-9mDDwqVIma6OZX79ZlDACZl8sBm0TEnkf99zV3iMA4GzkIT/9hiqP5mY0HoT1iNLCrKc/R1HByV+yJfRWVJryQ==}
-
lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
@@ -9147,10 +8714,6 @@ packages:
loupe@3.2.1:
resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==}
- lowercase-keys@1.0.1:
- resolution: {integrity: sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==}
- engines: {node: '>=0.10.0'}
-
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
@@ -9158,9 +8721,6 @@ packages:
resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==}
engines: {node: 20 || >=22}
- lru-cache@2.7.3:
- resolution: {integrity: sha512-WpibWJ60c3AgAz8a2iYErDrcT2C7OmKnsWhIcHOjkUHFjkXncJhtLxNSqUmxRxRunpb5I8Vprd7aNSd2NtksJQ==}
-
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
@@ -9168,9 +8728,6 @@ packages:
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
engines: {node: '>=10'}
- lru-queue@0.1.0:
- resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==}
-
lucide-react@0.447.0:
resolution: {integrity: sha512-SZ//hQmvi+kDKrNepArVkYK7/jfeZ5uFNEnYmd45RKZcbGD78KLnrcNXmgeg6m+xNHFvTG+CblszXCy4n6DN4w==}
peerDependencies:
@@ -9278,17 +8835,10 @@ packages:
media-tracks@0.3.3:
resolution: {integrity: sha512-9P2FuUHnZZ3iji+2RQk7Zkh5AmZTnOG5fODACnjhCVveX1McY3jmCRHofIEI+yTBqplz7LXy48c7fQ3Uigp88w==}
- media-typer@0.3.0:
- resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
- engines: {node: '>= 0.6'}
-
media-typer@1.1.0:
resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==}
engines: {node: '>= 0.8'}
- memoizee@0.3.10:
- resolution: {integrity: sha512-LLzVUuWwGBKK188spgOK/ukrp5zvd9JGsiLDH41pH9vt5jvhZfsu5pxDuAnYAMG8YEGce72KO07sSBy9KkvOfw==}
-
mendoza@3.0.8:
resolution: {integrity: sha512-iwxgEpSOx9BDLJMD0JAzNicqo9xdrvzt6w/aVwBKMndlA6z/DH41+o60H2uHB0vCR1Xr37UOgu9xFWJHvYsuKw==}
engines: {node: '>=14.18'}
@@ -9308,14 +8858,6 @@ packages:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
- method-override@2.3.10:
- resolution: {integrity: sha512-Ks2/7e+3JuwQcpLybc6wTHyqg13HDjOhLcE+YaAEub9DbSxF+ieMvxUlybmWW9luRMh9Cd0rO9aNtzUT51xfNQ==}
- engines: {node: '>= 0.8.0'}
-
- methods@1.1.2:
- resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
- engines: {node: '>= 0.6'}
-
micromark-core-commonmark@2.0.3:
resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==}
@@ -9419,10 +8961,6 @@ packages:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
- mime-db@1.12.0:
- resolution: {integrity: sha512-5aMAW7I4jZoZB27fXRuekqc4DVvJ7+hM8UcWrNj2mqibE54gXgPSonBYBdQW5hyaVNGmiYjY0ZMqn9fBefWYvA==}
- engines: {node: '>= 0.6'}
-
mime-db@1.33.0:
resolution: {integrity: sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==}
engines: {node: '>= 0.6'}
@@ -9435,10 +8973,6 @@ packages:
resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
engines: {node: '>= 0.6'}
- mime-types@2.0.14:
- resolution: {integrity: sha512-2ZHUEstNkIf2oTWgtODr6X0Cc4Ns/RN/hktdozndiEhhAC2wxXejF1FH0XLHTEImE9h6gr/tcnr3YOnSGsxc7Q==}
- engines: {node: '>= 0.6'}
-
mime-types@2.1.18:
resolution: {integrity: sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==}
engines: {node: '>= 0.6'}
@@ -9451,9 +8985,6 @@ packages:
resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==}
engines: {node: '>= 0.6'}
- mime@1.2.11:
- resolution: {integrity: sha512-Ysa2F/nqTNGHhhm9MV8ure4+Hc+Y8AWiqUdHxsO7xu8zc92ND9f3kpALHjaP026Ft17UfxrMt95c50PLUeynBw==}
-
mimic-fn@2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
@@ -9477,22 +9008,10 @@ packages:
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
engines: {node: '>=4'}
- minimatch@0.3.0:
- resolution: {integrity: sha512-WFX1jI1AaxNTZVOHLBVazwTWKaQjoykSzCBNXB72vDTCzopQGtyP91tKdFK5cv1+qMwPyiTu1HqUriqplI8pcA==}
- deprecated: Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue
-
- minimatch@0.4.0:
- resolution: {integrity: sha512-yJKJL1g3to7f4C/9LzHXTzNh550xKGefiCls9RS+DDdsDpKpndY49UDZW5sj/3yeac3Hl2Px3w5bT8bM/dMrWQ==}
- deprecated: Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue
-
minimatch@10.0.3:
resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==}
engines: {node: 20 || >=22}
- minimatch@2.0.10:
- resolution: {integrity: sha512-jQo6o1qSVLEWaw3l+bwYA2X0uLuK2KjNh2wjgO7Q/9UJnXr1Q3yQKR8BI0/Bt/rPg75e6SMW4hW/6cBHVTZUjA==}
- deprecated: Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue
-
minimatch@3.0.8:
resolution: {integrity: sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==}
@@ -9515,9 +9034,6 @@ packages:
resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==}
engines: {node: '>= 6'}
- minimist@0.0.10:
- resolution: {integrity: sha512-iotkTvxc+TwOm5Ieim8VnSNvCDjCK9S8G3scJ50ZthspSxa7jx50jkhYduuAtAjvfDUwSgOwf8+If99AlOEhyw==}
-
minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
@@ -9540,10 +9056,6 @@ packages:
mkdirp-classic@0.5.3:
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
- mkdirp@0.5.6:
- resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
- hasBin: true
-
mkdirp@3.0.1:
resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==}
engines: {node: '>=10'}
@@ -9558,13 +9070,6 @@ packages:
module-alias@2.2.3:
resolution: {integrity: sha512-23g5BFj4zdQL/b6tor7Ji+QY4pEfNH784BMslY9Qb0UnJWRAt+lQGLYmRaM0KDBwIG23ffEBELhZDP2rhi9f/Q==}
- moment@2.0.0:
- resolution: {integrity: sha512-l3jCjCJfC6pIuca13GMpCU4liEYvYk17XpFgpJSyLp8RX+dwdgrZBvtkaofmIigplezbG1VZFUgQbDFRSd37YA==}
-
- morgan@1.4.1:
- resolution: {integrity: sha512-miTjw0gjk8JEP8IMGOR5YwLZVFg1GLvxbTp84tzx632PlysK91fSxKHQpTaran/lJPvSl8Hhj7LuWRt2x/h2SQ==}
- engines: {node: '>= 0.8.0'}
-
motion-dom@12.23.21:
resolution: {integrity: sha512-5xDXx/AbhrfgsQmSE7YESMn4Dpo6x5/DTZ4Iyy4xqDvVHWvFVoV+V2Ri2S/ksx+D40wrZ7gPYiMWshkdoqNgNQ==}
@@ -9575,22 +9080,12 @@ packages:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'}
- ms@0.6.2:
- resolution: {integrity: sha512-/pc3eh7TWorTtbvXg8je4GvrvEqCfH7PA3P7iW01yL2E53FKixzgMBaQi0NOPbMJqY34cBSvR0tZtmlTkdUG4A==}
-
- ms@0.7.0:
- resolution: {integrity: sha512-YmuMMkfOZzzAftlHwiQxFepJx/5rDaYi9o9QanyBCk485BRAyM/vB9XoYlZvglxE/pmAWOiQgrdoE10watiK9w==}
-
ms@2.0.0:
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
- multiparty@3.3.2:
- resolution: {integrity: sha512-FX6dDOKzDpkrb5/+Imq+V6dmCZNnC02tMDiZfrgHSYgfQj6CVPGzOVqfbHKt/Vy4ZZsmMPXkulyLf92lCyvV7A==}
- engines: {node: '>=0.8.0'}
-
mute-stream@0.0.8:
resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==}
@@ -9604,9 +9099,6 @@ packages:
mux-embed@5.9.0:
resolution: {integrity: sha512-wmunL3uoPhma/tWy8PrDPZkvJpXvSFBwbD3KkC4PG8Ztjfb1X3hRJwGUAQyRz7z99b/ovLm2UTTitrkvStjH4w==}
- mz@1.3.0:
- resolution: {integrity: sha512-x+R7YSsEySSpV5uEB+C47JTmxv+YKKNsW3W+hjvq8NbLn8ntLgYXGrR5RjQ3Fs0e7Chw8Rp/1e5eo0n5LP76cw==}
-
nano-pubsub@3.0.0:
resolution: {integrity: sha512-zoTNyBafxG0+F5PP3T3j1PKMr7gedriSdYRhLFLRFCz0OnQfQ6BkVk9peXVF30hz633Bw0Zh5McleOrXPjWYCQ==}
engines: {node: '>=18'}
@@ -9626,37 +9118,13 @@ packages:
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
hasBin: true
- native-or-bluebird@1.1.2:
- resolution: {integrity: sha512-Bgn5FHNkd+lPTjIzq1NVU/VZTvPKFvhdIDEyYjxrKNrScSXbVvNVzOKwoleysun0/HoN7R+TXmK9mCtEs84osA==}
- deprecated: '''native-or-bluebird'' is deprecated. Please use ''any-promise'' instead.'
-
- native-or-bluebird@1.2.0:
- resolution: {integrity: sha512-0SH8UubxDfe382eYiwmd12qxAbiWGzlGZv6CkMA+DPojWa/Y0oH4hE0lRtFfFgJmPQFyKXeB8XxPbZz6TvvKaQ==}
- deprecated: '''native-or-bluebird'' is deprecated. Please use ''any-promise'' instead.'
-
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
- negotiator@0.4.9:
- resolution: {integrity: sha512-fvi5GQce2TGDzanaTxNY3bboxjdce18sqwNylY439wkEkiJIyTMhGFMdlPCvDsIPa9IKIfhKwCMWEQ9YpZgb1Q==}
- engines: {node: '>= 0.6'}
-
negotiator@1.0.0:
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
engines: {node: '>= 0.6'}
- neo-async@2.6.2:
- resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
-
- nested-error-stacks@1.0.2:
- resolution: {integrity: sha512-o32anp9JA7oezPOFSfG2BBXSdHepOm5FpJvwxHWDtfJ3Bg3xdi68S6ijPlEOfUg6quxZWyvJM+8fHk1yMDKspA==}
-
- next-tick@0.2.2:
- resolution: {integrity: sha512-f7h4svPtl+QidoBv4taKXUjJ70G2asaZ8G28nS0OkqaalX8dwwrtWtyxEDPK62AC00ur/+/E0pUwBwY5EPn15Q==}
-
- next-tick@1.1.0:
- resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
-
next@15.2.3:
resolution: {integrity: sha512-x6eDkZxk2rPpu46E1ZVUWIBhYCLszmUY6fvHBFcbzJ9dD+qRX6vcHusaqqDlnY+VngKzKbAiG2iRCkPbmi8f7w==}
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
@@ -9743,6 +9211,10 @@ packages:
sass:
optional: true
+ node-addon-api@8.6.0:
+ resolution: {integrity: sha512-gBVjCaqDlRUk0EwoPNKzIr9KkS9041G/q31IBShPs1Xz6UTA+EXdZADbzqAJQrpDRq71CIMnOP5VMut3SL0z5Q==}
+ engines: {node: ^18 || ^20 || >= 21}
+
node-fetch@2.7.0:
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
engines: {node: 4.x || >=6.0.0}
@@ -9767,15 +9239,6 @@ packages:
engines: {node: '>=10'}
hasBin: true
- nodewatch@0.3.2:
- resolution: {integrity: sha512-99ce9ao3KrUHRkRSSqmWk8xf7ulZTy5533qeF6l1mD3z1Cr5EZDZQnsd8KfSW5Fxc8s5w2fkLzloWqHBHwmDgg==}
- engines: {node: '>=0.4.0'}
- deprecated: Obsolete
-
- nopt@3.0.6:
- resolution: {integrity: sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==}
- hasBin: true
-
normalize-package-data@2.5.0:
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
@@ -9824,10 +9287,6 @@ packages:
nwsapi@2.2.22:
resolution: {integrity: sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==}
- object-assign@3.0.0:
- resolution: {integrity: sha512-jHP15vXVGeVh1HuaA2wY6lxk+whK/x4KBG88VXeRma7CCun7iGD5qPc4eYykQ9sdQvg8jkwFKsSxHln2ybW3xQ==}
- engines: {node: '>=0.10.0'}
-
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
@@ -9870,18 +9329,10 @@ packages:
resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
engines: {node: '>=14.0.0'}
- on-finished@2.1.1:
- resolution: {integrity: sha512-3ljOi5Zrf46pSbY/39CaJulZQN9XRfmeWqXkeWddhhKD7B4n7nOTisLdaZmAXI1P3A57peTj4pHokMY8X7ICCA==}
- engines: {node: '>= 0.8'}
-
on-finished@2.4.1:
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
engines: {node: '>= 0.8'}
- on-headers@1.0.2:
- resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==}
- engines: {node: '>= 0.8'}
-
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
@@ -9909,23 +9360,6 @@ packages:
resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==}
engines: {node: '>=12'}
- optimist@0.3.7:
- resolution: {integrity: sha512-TCx0dXQzVtSCg2OgY/bO9hjM9cV4XYx09TVK+s3+FhkjT6LovsLe+pPMzpWf+6yXK/hUizs2gUoTw3jHM0VaTQ==}
-
- optimist@0.5.2:
- resolution: {integrity: sha512-r9M8ZpnM9SXV5Wii7TCqienfcaY3tAiJe9Jchof87icbmbruKgK0xKXngmrnowTDnEawmmI1Qbha59JEoBkBGA==}
-
- optimist@0.6.1:
- resolution: {integrity: sha512-snN4O4TkigujZphWLN0E//nQmm7790RYaE53DdL7ZYwee2D8DDo9/EyYiKUfN3rneWUjhJnueija3G9I2i0h3g==}
-
- optional-color-logger@0.0.6:
- resolution: {integrity: sha512-5soulFgwrhtOUuBq7eSbrkCCNnkRmjv1EkRuel3XAHZixGg/pjJOXYaF5pj6ChXkywDMd/J1fwLk7KRMM6tv0Q==}
- engines: {node: '>= 0.8.0'}
-
- optionator@0.5.0:
- resolution: {integrity: sha512-jUr7aBk/kCInAEsl+qxuw4ORpe458atDKXNLhyvPUD4NfnsJsbAViX1b9nb/0rS62lO8cIFd1VoiaXLQ+MybOw==}
- engines: {node: '>= 0.8.0'}
-
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
@@ -9946,10 +9380,6 @@ packages:
resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==}
engines: {node: '>=0.10.0'}
- osenv@0.1.5:
- resolution: {integrity: sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==}
- deprecated: This package is no longer supported.
-
outdent@0.5.0:
resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==}
@@ -10055,10 +9485,6 @@ packages:
package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
- package-json@1.2.0:
- resolution: {integrity: sha512-knDtirWWqKVJrLY3gEBLflVvueTMpyjbAwX/9j/EKi2DsjNemp5voS8cyKyGh57SNaMJNhNRZbIaWdneOcLU1g==}
- engines: {node: '>=0.10.0'}
-
package-manager-detector@0.2.11:
resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==}
@@ -10189,9 +9615,6 @@ packages:
resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
engines: {node: '>= 14.16'}
- pause@0.0.1:
- resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==}
-
peek-stream@1.1.3:
resolution: {integrity: sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==}
@@ -10217,14 +9640,6 @@ packages:
resolution: {integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==}
engines: {node: '>=10'}
- pinkie-promise@2.0.1:
- resolution: {integrity: sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==}
- engines: {node: '>=0.10.0'}
-
- pinkie@2.0.4:
- resolution: {integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==}
- engines: {node: '>=0.10.0'}
-
pino-abstract-transport@2.0.0:
resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==}
@@ -10527,18 +9942,10 @@ packages:
resolution: {integrity: sha512-rU+ZAv1Ur9jAUZtGPebQVQPzdGhNzaEiQ7VL9+cjsAWPHFYOccNXPNiev1CCDSOg/2j7UujM7ojNhpkuILEVNQ==}
engines: {node: '>=18.12'}
- prelude-ls@1.1.2:
- resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==}
- engines: {node: '>= 0.8.0'}
-
prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
- prepend-http@1.0.4:
- resolution: {integrity: sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==}
- engines: {node: '>=0.10.0'}
-
prettier-linter-helpers@1.0.0:
resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
engines: {node: '>=6.0.0'}
@@ -10602,9 +10009,6 @@ packages:
resolution: {integrity: sha512-VWQJyU2bcDTgZw8kpfBpB/ejZASlCrzwz5f2hjb/zlujOEB4oeiAhHygAWq8ubsX2GVkD4kCU5V2dwOTaCY5EQ==}
engines: {node: '>=0.12'}
- promise@8.3.0:
- resolution: {integrity: sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==}
-
prompts@2.4.2:
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
engines: {node: '>= 6'}
@@ -10647,17 +10051,6 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
- q@1.4.1:
- resolution: {integrity: sha512-/CdEdaw49VZVmyIDGUQKDDT53c7qBkO6g5CefWz91Ae+l4+cRtcDYwMTXh6me4O8TMldeGHG3N2Bl84V78Ywbg==}
- engines: {node: '>=0.6.0', teleport: '>=0.2.0'}
- deprecated: |-
- You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.
-
- (For a CapTP with native promises, see @endo/eventual-send and @endo/captp)
-
- qs@2.3.3:
- resolution: {integrity: sha512-f5M0HQqZWkzU8GELTY8LyMrGkr3bPjKoFtTkwUEqJQbcljbeK8M7mliP9Ia2xoOI6oMerp+QPS7oYJtpGmWe/A==}
-
qs@6.14.0:
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
engines: {node: '>=0.6'}
@@ -10682,19 +10075,12 @@ packages:
resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
engines: {node: '>=10'}
- quote@0.4.0:
- resolution: {integrity: sha512-KHp3y3xDjuBhRx+tYKOgzPnVHMRlgpn2rU450GcU4PL24r1H6ls/hfPrxDwX2pvYMlwODHI2l8WwgoV69x5rUQ==}
-
raf@3.4.1:
resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==}
randombytes@2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
- range-parser@1.0.3:
- resolution: {integrity: sha512-nDsRrtIxVUO5opg/A8T2S3ebULVIfuh8ECbh4w3N4mWxIiT3QILDJDUQayPqm2e8Q8NUa0RSUkGCfe33AfjR3Q==}
- engines: {node: '>= 0.6'}
-
range-parser@1.2.0:
resolution: {integrity: sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==}
engines: {node: '>= 0.6'}
@@ -10703,11 +10089,6 @@ packages:
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
engines: {node: '>= 0.6'}
- raw-body@1.3.1:
- resolution: {integrity: sha512-x8EmVKh0fEOk/mlAl4P0NsLW6zH90FeXNGiOpFlvd1JPZH/3q4Wcngev7FI5Z5z8pjTn/1or0sAtxvF0558Dew==}
- engines: {node: '>= 0.8.0'}
- deprecated: No longer maintained. Please upgrade to a stable version.
-
raw-body@3.0.1:
resolution: {integrity: sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==}
engines: {node: '>= 0.10'}
@@ -10869,10 +10250,6 @@ packages:
resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==}
engines: {node: '>=0.10.0'}
- read-all-stream@3.1.0:
- resolution: {integrity: sha512-DI1drPHbmBcUDWrJ7ull/F2Qb8HkwBncVx8/RpKYFSIACYaVRQReISYPdZz/mt1y1+qMCOrfReTopERmaxtP6w==}
- engines: {node: '>=0.10.0'}
-
read-pkg-up@7.0.1:
resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==}
engines: {node: '>=8'}
@@ -10946,9 +10323,6 @@ packages:
regenerate@1.4.2:
resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==}
- regexp-quote@0.0.0:
- resolution: {integrity: sha512-R9elSUXyJeACXh7raO50vvfZyTdQ1bxYm0KlN2XD8eEzPAnedJTHMk4F7lCaggu9DrfUWP3CDUjAgyW1f2DpgA==}
-
regexp.prototype.flags@1.5.4:
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
engines: {node: '>= 0.4'}
@@ -10961,10 +10335,6 @@ packages:
resolution: {integrity: sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==}
engines: {node: '>=14'}
- registry-url@3.1.0:
- resolution: {integrity: sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==}
- engines: {node: '>=0.10.0'}
-
registry-url@5.1.0:
resolution: {integrity: sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==}
engines: {node: '>=8'}
@@ -10988,11 +10358,6 @@ packages:
remark-stringify@11.0.0:
resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
- repeating@1.1.3:
- resolution: {integrity: sha512-Nh30JLeMHdoI+AsQ5eblhZ7YlTsM9wiJQe/AHIunlK3KWzvXhXb36IJ7K1IOeRjIOtzMjdUHjwXUFxKJoPTSOg==}
- engines: {node: '>=0.10.0'}
- hasBin: true
-
require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
@@ -11025,12 +10390,6 @@ packages:
resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==}
engines: {node: '>=10'}
- resolve@0.7.4:
- resolution: {integrity: sha512-zxmAcifDjKxmUbk7chQdKhDSn8ml08g+MYyU37xhEXBp+N81cfbYsm4e0Gn9jtLbAvbR8w8Ox09xqUZtPuCoeA==}
-
- resolve@1.1.7:
- resolution: {integrity: sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==}
-
resolve@1.22.10:
resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==}
engines: {node: '>= 0.4'}
@@ -11045,10 +10404,6 @@ packages:
resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==}
hasBin: true
- response-time@2.2.0:
- resolution: {integrity: sha512-NusrwfV6rxbsmJSOutgsHT9MEipXlyO3niOTnyzTsGEIowds79TnRK0U+HQSQFTZH2PxOli0awDXguU48LgTNA==}
- engines: {node: '>= 0.8.0'}
-
restore-cursor@3.1.0:
resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
engines: {node: '>=8'}
@@ -11080,9 +10435,6 @@ packages:
engines: {node: 20 || >=22}
hasBin: true
- rndm@1.1.1:
- resolution: {integrity: sha512-0hmr77ro4CsIi6ywLsZ+QqUrwll5DHFdmgeKtf9cBBhiW47BHL7VG6bzhdepnwH4gL3gREnLuP2gCiPRnep5cw==}
-
rolldown-plugin-dts@0.16.8:
resolution: {integrity: sha512-lsx7yrYA0ZXfARLEcPKgHIw8DX4fLQOhmMChgZbn5eFhqibY2Bav1+/Yn5WNm+ATtw+cefXYgEYO/7njeHsgAA==}
engines: {node: '>=20.18.0'}
@@ -11243,20 +10595,12 @@ packages:
scheduler@0.27.0:
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
- scmp@1.0.0:
- resolution: {integrity: sha512-gCzsBFLpXrXnq60hYFV4hc4b5a3nIWTKtFWMYvlcXqs5gHKTR445CO3QbFRZW/O+9tRIVTeC46/MXbq1Se/1Sw==}
- deprecated: scmp v2 uses improved core crypto comparison since Node v6.6.0
-
scroll-into-view-if-needed@3.1.0:
resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==}
scrollmirror@1.2.4:
resolution: {integrity: sha512-UkEHHOV6j5cE3IsObQRK6vO4twSuhE4vtLD4UmX+doHgrtg2jRwXkz4O6cz0jcoxK5NGU7rFjyvLcWHzw7eQ5A==}
- semver-diff@2.1.0:
- resolution: {integrity: sha512-gL8F8L4ORwsS0+iQ34yCYv///jsOq0ZL7WP55d1HnJ32o7tyFYEFQZQA22mrLIacZdU6xecaBBZ+uEiffGNyXw==}
- engines: {node: '>=0.10.0'}
-
semver@5.7.2:
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
hasBin: true
@@ -11280,10 +10624,6 @@ packages:
engines: {node: '>=10'}
hasBin: true
- send@0.10.1:
- resolution: {integrity: sha512-dSL7VfFGv0Du8qj0YntGl552UjWgZxTfFrBvngjc1wDPncyZnukfbGKWLW/Eo7qNlEbm6cUbLeCJBH9LJ/cDPQ==}
- engines: {node: '>= 0.8.0'}
-
send@1.2.0:
resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==}
engines: {node: '>= 18'}
@@ -11301,21 +10641,9 @@ packages:
resolution: {integrity: sha512-OE4cvmJ1uSPrKorFIH9/w/Qwuvi/IMcGbv5RKgcJ/zjA/IohDLU6SVaxFN9FwajbP7nsX0dQqMDes1whk3y+yw==}
engines: {node: '>=10'}
- serve-favicon@2.1.7:
- resolution: {integrity: sha512-I8IEBWpiW6JhCdxaMSY0/y8cCydtvOhIvd+Ucd11jMdThplj96WNz5jnUuODk05wMLUybDB1DKLezpW5BgUQtA==}
- engines: {node: '>= 0.8.0'}
-
serve-handler@6.1.6:
resolution: {integrity: sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==}
- serve-index@1.5.3:
- resolution: {integrity: sha512-d7b3XREkgYESxL1Cti3AlI/Lbr5Bh8Bih7jGGMLUnwKCmZqe6zbweFcg5ywDeNnyFHjggQy+gR30Sj3aNvhxPw==}
- engines: {node: '>= 0.8.0'}
-
- serve-static@1.7.2:
- resolution: {integrity: sha512-/PaWqZFkjFYXqGLOs6nc0ameuFkzzU0MrdKt6oxdv7EZ7Dx2/X2LKMu7VZs8/oHt2//+bBI2z+Stq0EE0F9z1g==}
- engines: {node: '>= 0.8.0'}
-
serve-static@2.2.0:
resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==}
engines: {node: '>= 18'}
@@ -11390,9 +10718,6 @@ packages:
siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
- sigmund@1.0.1:
- resolution: {integrity: sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==}
-
signal-exit@3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
@@ -11437,12 +10762,13 @@ packages:
slate@0.118.1:
resolution: {integrity: sha512-6H1DNgnSwAFhq/pIgf+tLvjNzH912M5XrKKhP9Frmbds2zFXdSJ6L/uFNyVKxQIkPzGWPD0m+wdDfmEuGFH5Tg==}
- slide@1.1.6:
- resolution: {integrity: sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==}
-
smob@1.5.0:
resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==}
+ smol-toml@1.6.0:
+ resolution: {integrity: sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==}
+ engines: {node: '>= 18'}
+
sonic-boom@4.2.0:
resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==}
@@ -11461,14 +10787,6 @@ packages:
source-map-support@0.5.21:
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
- source-map@0.1.43:
- resolution: {integrity: sha512-VtCvB9SIQhk3aF6h+N85EaqIaBFIAfZ9Cu+NJHHVvc8BbEcnvDcFw6sqQ2dQrT6SlOrZq3tIvyD9+EGq/lJryQ==}
- engines: {node: '>=0.8.0'}
-
- source-map@0.2.0:
- resolution: {integrity: sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA==}
- engines: {node: '>=0.8.0'}
-
source-map@0.6.1:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
@@ -11518,11 +10836,6 @@ packages:
sprintf-js@1.0.3:
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
- sprintf@0.1.5:
- resolution: {integrity: sha512-4X5KsuXFQ7f+d7Y+bi4qSb6eI+YoifDTGr0MQJXRoYO7BO7evfRCjds6kk3z7l5CiJYxgDN1x5Er4WiyCt+zTQ==}
- engines: {node: '>=0.2.4'}
- deprecated: The sprintf package is deprecated in favor of sprintf-js.
-
srvx@0.11.4:
resolution: {integrity: sha512-m/2p87bqWZ94xpRN06qNBwh0xq/D0dXajnvPDSHFqrTogxuTWYNP1UHz6Cf+oY7D+NPLY35TJAp4ESIKn0WArQ==}
engines: {node: '>=20.16.0'}
@@ -11542,10 +10855,6 @@ packages:
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
- statuses@1.5.0:
- resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==}
- engines: {node: '>= 0.6'}
-
statuses@2.0.1:
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
engines: {node: '>= 0.8'}
@@ -11565,10 +10874,6 @@ packages:
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
engines: {node: '>= 0.4'}
- stream-counter@0.2.0:
- resolution: {integrity: sha512-GjA2zKc2iXUUKRcOxXQmhEx0Ev3XHJ6c8yWGqhQjWwhGrqNwSsvq9YlRLgoGtZ5Kx2Ln94IedaqJ5GUG6aBbxA==}
- engines: {node: '>=0.8.0'}
-
stream-each@1.2.3:
resolution: {integrity: sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==}
@@ -11589,10 +10894,6 @@ packages:
string-hash@1.1.3:
resolution: {integrity: sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==}
- string-length@1.0.1:
- resolution: {integrity: sha512-MNCACnufWUf3pQ57O5WTBMkKhzYIaKEcUioO0XHrTMafrbBaNk4IyDOLHBv5xbXO0jLLdsYWeFjpjG2hVHRDtw==}
- engines: {node: '>=0.10.0'}
-
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
@@ -11640,10 +10941,6 @@ packages:
stringify-entities@4.0.4:
resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}
- strip-ansi@3.0.1:
- resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==}
- engines: {node: '>=0.10.0'}
-
strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
@@ -11714,19 +11011,6 @@ packages:
stylis@4.3.2:
resolution: {integrity: sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==}
- supports-color@1.3.1:
- resolution: {integrity: sha512-OHbMkscHFRcNWEcW80fYhCrzAjheSIBwJChpFaBqA6zEz53nxumqi6ukciRb/UA0/v2nDNMk28ce/uBbYRDsng==}
- engines: {node: '>=0.8.0'}
- hasBin: true
-
- supports-color@2.0.0:
- resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==}
- engines: {node: '>=0.8.0'}
-
- supports-color@3.2.3:
- resolution: {integrity: sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==}
- engines: {node: '>=0.8.0'}
-
supports-color@5.5.0:
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
engines: {node: '>=4'}
@@ -11806,13 +11090,6 @@ packages:
text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
- thenify-all@1.6.0:
- resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
- engines: {node: '>=0.8'}
-
- thenify@3.3.1:
- resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
-
thread-stream@3.1.0:
resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
@@ -11828,14 +11105,6 @@ packages:
through@2.3.8:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
- timed-out@2.0.0:
- resolution: {integrity: sha512-pqqJOi1rF5zNs/ps4vmbE4SFCrM4iR7LW+GHAsHqO/EumqbIWceioevYLM5xZRgQSH6gFgL9J/uB7EcJhQ9niQ==}
- engines: {node: '>=0.10.0'}
-
- timers-ext@0.1.8:
- resolution: {integrity: sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==}
- engines: {node: '>=0.12'}
-
tiny-invariant@1.3.1:
resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==}
@@ -11940,6 +11209,14 @@ packages:
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
hasBin: true
+ tree-sitter-python@0.25.0:
+ resolution: {integrity: sha512-eCmJx6zQa35GxaCtQD+wXHOhYqBxEL+bp71W/s3fcDMu06MrtzkVXR437dRrCrbrDbyLuUDJpAgycs7ncngLXw==}
+ peerDependencies:
+ tree-sitter: ^0.25.0
+ peerDependenciesMeta:
+ tree-sitter:
+ optional: true
+
treeify@1.1.0:
resolution: {integrity: sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==}
engines: {node: '>=0.6'}
@@ -12049,10 +11326,6 @@ packages:
resolution: {integrity: sha512-5c9Fdsr9qfpT3hA0EyYSFRZj1dVVsb6KIWubA9JBYZ/9ZEAijgUEae0BBR/Xl/wekt4w65/lYLTFaP3JmwSO8w==}
hasBin: true
- type-check@0.3.2:
- resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==}
- engines: {node: '>= 0.8.0'}
-
type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
@@ -12081,17 +11354,10 @@ packages:
resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==}
engines: {node: '>=8'}
- type-is@1.5.7:
- resolution: {integrity: sha512-of68V0oUmVH4thGc1cLR3sKdICPsaL7kzpYc7FX1pcagY4eIllhyMqQcoOq289f+xj2orm8oPWwsCwxiCgVJbQ==}
- engines: {node: '>= 0.6'}
-
type-is@2.0.1:
resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==}
engines: {node: '>= 0.6'}
- type@2.7.3:
- resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==}
-
typed-array-buffer@1.0.3:
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
engines: {node: '>= 0.4'}
@@ -12142,23 +11408,6 @@ packages:
ufo@1.6.1:
resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
- uglify-js@2.3.6:
- resolution: {integrity: sha512-T2LWWydxf5+Btpb0S/Gg/yKFmYjnX9jtQ4mdN9YRq73BhN21EhU0Dvw3wYDLqd3TooGUJlCKf3Gfyjjy/RTcWA==}
- engines: {node: '>=0.4.0'}
- hasBin: true
-
- uglify-js@3.19.3:
- resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==}
- engines: {node: '>=0.8.0'}
- hasBin: true
-
- uid-safe@1.0.1:
- resolution: {integrity: sha512-+gRoyiC2ZLfhUZDL5VrKFCWNirAe/MTTiVhhZ3S0QGw35KqOFbagZcUSzZOgXR3aazpN/zAYJcaQ54sFlU5tkA==}
-
- uid-safe@1.1.0:
- resolution: {integrity: sha512-7+QtWs9zioL/iQX61G+4h3EPyr3H+tINIp0IAV4EL32vdf7qmFyuW0BgRqWl7p5oZOsEQrlL0bY7m5D8tp7b1w==}
- engines: {node: '>= 0.8'}
-
unbox-primitive@1.1.0:
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
engines: {node: '>= 0.4'}
@@ -12267,21 +11516,12 @@ packages:
unrs-resolver@1.11.1:
resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==}
- untested@0.1.5:
- resolution: {integrity: sha512-N7hB2AvWA58nb3JfHfGtFlKJDmtiTD7V7czJbY9jpPRhaRbjtjDWw/k/4EVXXORKTGW5EQiyj4jo+w9jY3OB/A==}
- engines: {node: '>= 0.8.0'}
- hasBin: true
-
update-browserslist-db@1.1.3:
resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==}
hasBin: true
peerDependencies:
browserslist: '>= 4.21.0'
- update-notifier@0.5.0:
- resolution: {integrity: sha512-zOGOlUKDAgDlLHLv7Oiszz3pSj8fKlSJ3i0u49sEakjXUEVJ6DMjo/Mh/B6mg2eOALvRTJkd0kbChcipQoYCng==}
- engines: {node: '>=0.10.0'}
-
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
@@ -12352,10 +11592,6 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
- utils-merge@1.0.0:
- resolution: {integrity: sha512-HwU9SLQEtyo+0uoKXd1nkLqigUWLB+QuNQR4OcmB73eWqksM5ovuqcycks2x043W8XVb75rG1HQ0h93TMXkzQQ==}
- engines: {node: '>= 0.4.0'}
-
uuid@11.1.0:
resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
hasBin: true
@@ -12364,10 +11600,6 @@ packages:
resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==}
hasBin: true
- uuid@2.0.3:
- resolution: {integrity: sha512-FULf7fayPdpASncVy4DLh3xydlXEJJpvIELjYjNeQWYUZ9pclcpvCZSr2gkmN2FrrGcI7G/cJsIEwk5/8vfXpg==}
- deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.
-
uuid@8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
@@ -12386,10 +11618,6 @@ packages:
resolution: {integrity: sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
- vary@1.0.1:
- resolution: {integrity: sha512-yNsH+tC0r8quK2tg/yqkXqqaYzeKTkSqQ+8T6xCoWgOi/bU/omMYz+6k+I91JJJDeltJzI7oridTOq6OYkY0Tw==}
- engines: {node: '>= 0.8'}
-
vary@1.1.2:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
@@ -12400,10 +11628,6 @@ packages:
vfile@6.0.3:
resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
- vhost@3.0.2:
- resolution: {integrity: sha512-S3pJdWrpFWrKMboRU4dLYgMrTgoPALsmYwOvyebK2M6X95b9kQrjZy5rwl3uzzpfpENe/XrNYu/2U+e7/bmT5g==}
- engines: {node: '>= 0.8.0'}
-
vite-node@1.6.1:
resolution: {integrity: sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==}
engines: {node: ^18.0.0 || >=20.0.0}
@@ -12595,6 +11819,9 @@ packages:
wcwidth@1.0.1:
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
+ web-tree-sitter@0.26.6:
+ resolution: {integrity: sha512-fSPR7VBW/fZQdUSp/bXTDLT+i/9dwtbnqgEBMzowrM4U3DzeCwDbY3MKo0584uQxID4m/1xpLflrlT/rLIRPew==}
+
webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
@@ -12649,14 +11876,6 @@ packages:
resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==}
engines: {node: '>= 0.4'}
- which@1.0.9:
- resolution: {integrity: sha512-E87fdQ/eRJr9W1X4wTPejNy9zTW3FI2vpCZSJ/HAY+TkjKVC0TUm1jk6vn2Z7qay0DQy0+RBGdXxj+RmmiGZKQ==}
- hasBin: true
-
- which@1.3.1:
- resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
- hasBin: true
-
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
@@ -12680,10 +11899,6 @@ packages:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
- wordwrap@0.0.3:
- resolution: {integrity: sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==}
- engines: {node: '>=0.4.0'}
-
wordwrap@1.0.0:
resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}
@@ -12702,9 +11917,6 @@ packages:
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
- write-file-atomic@1.3.4:
- resolution: {integrity: sha512-SdrHoC/yVBPpV0Xq/mUZQIpW2sWXAShb/V4pomcJXh92RuaO+f3UTWItiR3Px+pLnV2PvC2/bfn5cwr5X6Vfxw==}
-
write-file-atomic@3.0.3:
resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==}
@@ -12724,10 +11936,6 @@ packages:
resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==}
engines: {node: '>=18'}
- xdg-basedir@2.0.0:
- resolution: {integrity: sha512-NF1pPn594TaRSUO/HARoB4jK8I+rWgcpVlpQCK6/6o5PHyLUt2CSiDrpUZbQ6rROck+W2EwF8mBJcTs+W98J9w==}
- engines: {node: '>=0.10.0'}
-
xdg-basedir@4.0.0:
resolution: {integrity: sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==}
engines: {node: '>=8'}
@@ -13004,13 +12212,13 @@ snapshots:
'@babel/core@7.28.4':
dependencies:
'@babel/code-frame': 7.27.1
- '@babel/generator': 7.28.3
+ '@babel/generator': 7.28.6
'@babel/helper-compilation-targets': 7.27.2
'@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4)
'@babel/helpers': 7.28.4
- '@babel/parser': 7.28.4
+ '@babel/parser': 7.28.6
'@babel/template': 7.27.2
- '@babel/traverse': 7.28.4
+ '@babel/traverse': 7.28.6
'@babel/types': 7.28.4
'@jridgewell/remapping': 2.3.5
convert-source-map: 2.0.0
@@ -13132,7 +12340,7 @@ snapshots:
'@babel/helper-module-imports@7.27.1':
dependencies:
- '@babel/traverse': 7.28.4
+ '@babel/traverse': 7.28.6
'@babel/types': 7.28.4
transitivePeerDependencies:
- supports-color
@@ -13221,7 +12429,7 @@ snapshots:
'@babel/helper-wrap-function@7.28.3':
dependencies:
- '@babel/template': 7.27.2
+ '@babel/template': 7.28.6
'@babel/traverse': 7.28.6
'@babel/types': 7.28.6
transitivePeerDependencies:
@@ -13229,7 +12437,7 @@ snapshots:
'@babel/helpers@7.28.4':
dependencies:
- '@babel/template': 7.27.2
+ '@babel/template': 7.28.6
'@babel/types': 7.28.6
'@babel/helpers@7.28.6':
@@ -13385,7 +12593,7 @@ snapshots:
dependencies:
'@babel/core': 7.28.4
'@babel/helper-plugin-utils': 7.27.1
- '@babel/template': 7.27.2
+ '@babel/template': 7.28.6
'@babel/plugin-transform-destructuring@7.28.0(@babel/core@7.28.4)':
dependencies:
@@ -13904,7 +13112,7 @@ snapshots:
'@babel/template@7.27.2':
dependencies:
'@babel/code-frame': 7.27.1
- '@babel/parser': 7.28.4
+ '@babel/parser': 7.28.6
'@babel/types': 7.28.4
'@babel/template@7.28.6':
@@ -14706,27 +13914,27 @@ snapshots:
dependencies:
tslib: 2.8.1
- '@generaltranslation/compiler@1.1.18':
+ '@generaltranslation/compiler@1.1.25':
dependencies:
'@babel/generator': 7.28.6
'@babel/parser': 7.28.6
'@babel/traverse': 7.28.6
'@babel/types': 7.28.6
- generaltranslation: 8.1.9
+ generaltranslation: 8.1.14
unplugin: 2.3.10
transitivePeerDependencies:
- supports-color
- '@generaltranslation/react-core@1.4.7(react@19.2.3)':
+ '@generaltranslation/react-core@1.5.4(react@19.2.3)':
dependencies:
- '@generaltranslation/supported-locales': 2.0.40
- generaltranslation: 8.1.9
- gt-i18n: 0.3.7
+ '@generaltranslation/supported-locales': 2.0.47
+ generaltranslation: 8.1.14
+ gt-i18n: 0.4.2
react: 19.2.3
- '@generaltranslation/supported-locales@2.0.40':
+ '@generaltranslation/supported-locales@2.0.47':
dependencies:
- generaltranslation: 8.1.9
+ generaltranslation: 8.1.14
'@humanfs/core@0.19.1': {}
@@ -16385,7 +15593,7 @@ snapshots:
'@sanity/cli@4.10.1(@types/node@24.8.0)(bufferutil@4.0.9)(lightningcss@1.30.1)(react@19.2.3)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.2)(yaml@2.8.1)':
dependencies:
- '@babel/traverse': 7.28.4
+ '@babel/traverse': 7.28.6
'@sanity/client': 7.11.2(debug@4.4.3)
'@sanity/codegen': 4.10.1
'@sanity/runtime-cli': 10.9.0(@types/node@24.8.0)(bufferutil@4.0.9)(debug@4.4.3)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.2)(yaml@2.8.1)
@@ -17353,7 +16561,7 @@ snapshots:
'@types/babel__template@7.4.4':
dependencies:
- '@babel/parser': 7.28.4
+ '@babel/parser': 7.28.6
'@babel/types': 7.28.4
'@types/babel__traverse@7.28.0':
@@ -17422,10 +16630,6 @@ snapshots:
'@types/json5@0.0.29': {}
- '@types/keyv@3.1.4':
- dependencies:
- '@types/node': 20.19.17
-
'@types/lodash.merge@4.6.9':
dependencies:
'@types/lodash': 4.17.20
@@ -17504,10 +16708,6 @@ snapshots:
'@types/resolve@1.20.6': {}
- '@types/responselike@1.0.3':
- dependencies:
- '@types/node': 20.19.17
-
'@types/shallow-equals@1.0.3': {}
'@types/speakingurl@13.0.6': {}
@@ -18093,17 +17293,10 @@ snapshots:
transitivePeerDependencies:
- '@types/react'
- abbrev@1.0.9: {}
-
abort-controller@3.0.0:
dependencies:
event-target-shim: 5.0.1
- accepts@1.1.4:
- dependencies:
- mime-types: 2.0.14
- negotiator: 0.4.9
-
accepts@2.0.0:
dependencies:
mime-types: 3.0.1
@@ -18161,30 +17354,16 @@ snapshots:
require-from-string: 2.0.2
uri-js: 4.4.1
- allong.es@0.14.0:
- dependencies:
- promise: 8.3.0
-
- amdefine@0.1.1: {}
-
- amdefine@1.0.0: {}
-
ansi-colors@4.1.3: {}
ansi-escapes@4.3.2:
dependencies:
type-fest: 0.21.3
- ansi-regex@1.1.1: {}
-
- ansi-regex@2.1.1: {}
-
ansi-regex@5.0.1: {}
ansi-regex@6.2.2: {}
- ansi-styles@2.2.1: {}
-
ansi-styles@3.2.1:
dependencies:
color-convert: 1.9.3
@@ -18203,8 +17382,6 @@ snapshots:
ansis@4.2.0: {}
- any-promise@1.3.0: {}
-
anymatch@3.1.3:
dependencies:
normalize-path: 3.0.0
@@ -18321,8 +17498,6 @@ snapshots:
arrify@2.0.1: {}
- asap@2.0.6: {}
-
assertion-error@1.1.0: {}
assertion-error@2.0.1: {}
@@ -18350,15 +17525,6 @@ snapshots:
dependencies:
tslib: 2.8.1
- async@0.2.10:
- optional: true
-
- async@0.9.2: {}
-
- async@1.3.0: {}
-
- async@1.5.0: {}
-
async@3.2.6: {}
asynckit@0.4.0: {}
@@ -18425,18 +17591,8 @@ snapshots:
base64-js@1.5.1: {}
- base64-url@1.2.1: {}
-
- base64-url@1.3.3: {}
-
baseline-browser-mapping@2.8.9: {}
- basic-auth-connect@1.0.0: {}
-
- basic-auth@1.0.0: {}
-
- batch@0.5.1: {}
-
before-after-hook@2.2.3: {}
better-path-resolve@1.0.0:
@@ -18457,17 +17613,6 @@ snapshots:
inherits: 2.0.4
readable-stream: 3.6.2
- body-parser@1.9.3:
- dependencies:
- bytes: 1.0.0
- depd: 1.0.1
- iconv-lite: 0.4.5
- media-typer: 0.3.0
- on-finished: 2.1.1
- qs: 2.3.3
- raw-body: 1.3.1
- type-is: 1.5.7
-
body-parser@2.2.0:
dependencies:
bytes: 3.1.2
@@ -18540,8 +17685,6 @@ snapshots:
dependencies:
streamsearch: 1.1.0
- bytes@1.0.0: {}
-
bytes@3.0.0: {}
bytes@3.1.2: {}
@@ -18619,14 +17762,6 @@ snapshots:
loupe: 3.2.1
pathval: 2.0.1
- chalk@1.1.3:
- dependencies:
- ansi-styles: 2.2.1
- escape-string-regexp: 1.0.5
- has-ansi: 2.0.0
- strip-ansi: 3.0.1
- supports-color: 2.0.0
-
chalk@2.4.2:
dependencies:
ansi-styles: 3.2.1
@@ -18664,10 +17799,6 @@ snapshots:
check-error@2.1.1: {}
- check-types@0.6.5: {}
-
- check-types@1.4.0: {}
-
cheerio-select@2.1.0:
dependencies:
boolbase: 1.0.0
@@ -18725,24 +17856,6 @@ snapshots:
dependencies:
escape-string-regexp: 4.0.0
- cli-color@1.0.0:
- dependencies:
- ansi-regex: 1.1.1
- d: 0.1.1
- es5-ext: 0.10.64
- es6-iterator: 0.1.3
- memoizee: 0.3.10
- timers-ext: 0.1.8
-
- cli-color@1.1.0:
- dependencies:
- ansi-regex: 2.1.1
- d: 0.1.1
- es5-ext: 0.10.64
- es6-iterator: 2.0.3
- memoizee: 0.3.10
- timers-ext: 0.1.8
-
cli-cursor@3.1.0:
dependencies:
restore-cursor: 3.1.0
@@ -18755,10 +17868,6 @@ snapshots:
cli-spinners@3.3.0: {}
- cli-table@0.2.0:
- dependencies:
- colors: 0.3.0
-
cli-width@3.0.0: {}
cli-width@4.1.0: {}
@@ -18799,8 +17908,6 @@ snapshots:
- '@types/react'
- '@types/react-dom'
- coffee-script@1.6.3: {}
-
color-convert@1.9.3:
dependencies:
color-name: 1.1.3
@@ -18829,12 +17936,6 @@ snapshots:
colord@2.9.3: {}
- colors@0.3.0: {}
-
- colors@0.6.0-1: {}
-
- colors@0.6.2: {}
-
combined-stream@1.0.8:
dependencies:
delayed-stream: 1.0.0
@@ -18843,10 +17944,6 @@ snapshots:
comma-separated-tokens@2.0.3: {}
- commander@1.1.1:
- dependencies:
- keypress: 0.1.0
-
commander@12.1.0: {}
commander@13.1.0: {}
@@ -18865,21 +17962,6 @@ snapshots:
normalize-path: 3.0.0
readable-stream: 4.7.0
- compressible@2.0.18:
- dependencies:
- mime-db: 1.54.0
-
- compression@1.2.2:
- dependencies:
- accepts: 1.1.4
- bytes: 1.0.0
- compressible: 2.0.18
- debug: 2.1.3
- on-headers: 1.0.2
- vary: 1.0.1
- transitivePeerDependencies:
- - supports-color
-
compute-scroll-into-view@3.1.1: {}
concat-map@0.0.1: {}
@@ -18923,17 +18005,6 @@ snapshots:
ini: 1.3.8
proto-list: 1.2.4
- configstore@1.4.0:
- dependencies:
- graceful-fs: 4.2.11
- mkdirp: 0.5.6
- object-assign: 4.1.1
- os-tmpdir: 1.0.2
- osenv: 0.1.5
- uuid: 2.0.3
- write-file-atomic: 1.3.4
- xdg-basedir: 2.0.0
-
configstore@5.0.1:
dependencies:
dot-prop: 5.3.0
@@ -18943,57 +18014,10 @@ snapshots:
write-file-atomic: 3.0.3
xdg-basedir: 4.0.0
- connect-timeout@1.4.0:
- dependencies:
- debug: 2.1.3
- http-errors: 1.2.8
- ms: 0.6.2
- on-headers: 1.0.2
- transitivePeerDependencies:
- - supports-color
-
- connect@2.27.6:
- dependencies:
- basic-auth-connect: 1.0.0
- body-parser: 1.9.3
- bytes: 1.0.0
- compression: 1.2.2
- connect-timeout: 1.4.0
- cookie: 0.1.2
- cookie-parser: 1.3.5
- cookie-signature: 1.0.5
- csurf: 1.6.6
- debug: 2.1.3
- depd: 1.0.1
- errorhandler: 1.2.4
- express-session: 1.9.3
- finalhandler: 0.3.2
- fresh: 0.2.4
- http-errors: 1.2.8
- media-typer: 0.3.0
- method-override: 2.3.10
- morgan: 1.4.1
- multiparty: 3.3.2
- on-headers: 1.0.2
- parseurl: 1.3.3
- pause: 0.0.1
- qs: 2.3.3
- response-time: 2.2.0
- serve-favicon: 2.1.7
- serve-index: 1.5.3
- serve-static: 1.7.2
- type-is: 1.5.7
- utils-merge: 1.0.0
- vhost: 3.0.2
- transitivePeerDependencies:
- - supports-color
-
console-table-printer@2.14.6:
dependencies:
simple-wcswidth: 1.1.2
- console.json@0.2.1: {}
-
content-disposition@0.5.2: {}
content-disposition@1.0.0:
@@ -19006,21 +18030,8 @@ snapshots:
cookie-es@2.0.0: {}
- cookie-parser@1.3.5:
- dependencies:
- cookie: 0.1.3
- cookie-signature: 1.0.6
-
- cookie-signature@1.0.5: {}
-
- cookie-signature@1.0.6: {}
-
cookie-signature@1.2.2: {}
- cookie@0.1.2: {}
-
- cookie@0.1.3: {}
-
cookie@0.7.2: {}
core-js-compat@3.45.1:
@@ -19050,8 +18061,6 @@ snapshots:
crc-32: 1.2.2
readable-stream: 4.7.0
- crc@3.2.1: {}
-
create-require@1.1.1: {}
cross-spawn@7.0.6:
@@ -19064,13 +18073,6 @@ snapshots:
crypto-random-string@2.0.0: {}
- csrf@2.0.7:
- dependencies:
- base64-url: 1.2.1
- rndm: 1.1.1
- scmp: 1.0.0
- uid-safe: 1.1.0
-
css-color-keywords@1.0.0: {}
css-declaration-sorter@6.4.1(postcss@8.5.6):
@@ -19183,36 +18185,10 @@ snapshots:
csstype@3.2.3: {}
- csurf@1.6.6:
- dependencies:
- cookie: 0.1.2
- cookie-signature: 1.0.5
- csrf: 2.0.7
- http-errors: 1.2.8
-
- custom-logger@0.2.1:
- dependencies:
- colors: 0.6.0-1
- dateformat: 1.0.2-1.2.3
-
- custom-logger@0.3.1:
- dependencies:
- colors: 0.6.0-1
- dateformat: 1.0.2-1.2.3
-
custom-media-element@1.4.5: {}
cyclist@1.0.2: {}
- d@0.1.1:
- dependencies:
- es5-ext: 0.10.64
-
- d@1.0.2:
- dependencies:
- es5-ext: 0.10.64
- type: 2.7.3
-
damerau-levenshtein@1.0.8: {}
data-uri-to-buffer@1.2.0: {}
@@ -19255,14 +18231,8 @@ snapshots:
date-fns@4.1.0: {}
- dateformat@1.0.2-1.2.3: {}
-
debounce@1.2.1: {}
- debug@2.1.3:
- dependencies:
- ms: 0.7.0
-
debug@2.6.9:
dependencies:
ms: 2.0.0
@@ -19347,16 +18317,12 @@ snapshots:
delayed-stream@1.0.0: {}
- depd@1.0.1: {}
-
depd@2.0.0: {}
deprecation@2.3.1: {}
dequal@2.0.3: {}
- destroy@1.0.3: {}
-
detect-indent@6.1.0: {}
detect-indent@7.0.2: {}
@@ -19465,8 +18431,6 @@ snapshots:
eastasianwidth@0.2.0: {}
- ee-first@1.1.0: {}
-
ee-first@1.1.1: {}
ejs@3.1.10:
@@ -19514,11 +18478,6 @@ snapshots:
dependencies:
is-arrayish: 0.2.1
- errorhandler@1.2.4:
- dependencies:
- accepts: 1.1.4
- escape-html: 1.0.1
-
es-abstract@1.24.0:
dependencies:
array-buffer-byte-length: 1.0.2
@@ -19622,42 +18581,6 @@ snapshots:
is-date-object: 1.1.0
is-symbol: 1.1.1
- es5-ext@0.10.64:
- dependencies:
- es6-iterator: 2.0.3
- es6-symbol: 3.1.4
- esniff: 2.0.1
- next-tick: 1.1.0
-
- es6-iterator@0.1.3:
- dependencies:
- d: 0.1.1
- es5-ext: 0.10.64
- es6-symbol: 2.0.1
-
- es6-iterator@2.0.3:
- dependencies:
- d: 1.0.2
- es5-ext: 0.10.64
- es6-symbol: 3.1.4
-
- es6-symbol@2.0.1:
- dependencies:
- d: 0.1.1
- es5-ext: 0.10.64
-
- es6-symbol@3.1.4:
- dependencies:
- d: 1.0.2
- ext: 1.7.0
-
- es6-weak-map@0.1.4:
- dependencies:
- d: 0.1.1
- es5-ext: 0.10.64
- es6-iterator: 0.1.3
- es6-symbol: 2.0.1
-
esbuild-register@3.6.0(esbuild@0.24.2):
dependencies:
debug: 4.4.3(supports-color@5.5.0)
@@ -19786,8 +18709,6 @@ snapshots:
escalade@3.2.0: {}
- escape-html@1.0.1: {}
-
escape-html@1.0.3: {}
escape-string-regexp@1.0.5: {}
@@ -19798,33 +18719,7 @@ snapshots:
escape-string-regexp@5.0.0: {}
- escodegen@1.3.3:
- dependencies:
- esprima: 1.1.1
- estraverse: 1.5.1
- esutils: 1.0.0
- optionalDependencies:
- source-map: 0.1.43
-
- escodegen@1.6.1:
- dependencies:
- esprima: 1.2.5
- estraverse: 1.9.3
- esutils: 1.1.6
- optionator: 0.5.0
- optionalDependencies:
- source-map: 0.1.43
-
- escodegen@1.7.1:
- dependencies:
- esprima: 1.2.5
- estraverse: 1.9.3
- esutils: 2.0.3
- optionator: 0.5.0
- optionalDependencies:
- source-map: 0.2.0
-
- eslint-config-next@15.5.2(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2):
+ eslint-config-next@15.5.2(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2):
dependencies:
'@next/eslint-plugin-next': 15.5.2
'@rushstack/eslint-patch': 1.12.0
@@ -20152,13 +19047,6 @@ snapshots:
esm@3.2.25: {}
- esniff@2.0.1:
- dependencies:
- d: 1.0.2
- es5-ext: 0.10.64
- event-emitter: 0.3.5
- type: 2.7.3
-
espree@10.4.0:
dependencies:
acorn: 8.15.0
@@ -20171,14 +19059,6 @@ snapshots:
acorn-jsx: 5.3.2(acorn@8.15.0)
eslint-visitor-keys: 3.4.3
- esprima@1.1.1: {}
-
- esprima@1.2.5: {}
-
- esprima@2.4.1: {}
-
- esprima@2.5.0: {}
-
esprima@4.0.1: {}
esquery@1.6.0:
@@ -20189,10 +19069,6 @@ snapshots:
dependencies:
estraverse: 5.3.0
- estraverse@1.5.1: {}
-
- estraverse@1.9.3: {}
-
estraverse@5.3.0: {}
estree-util-is-identifier-name@3.0.0: {}
@@ -20210,16 +19086,8 @@ snapshots:
dependencies:
'@types/estree': 1.0.8
- esutils@1.0.0: {}
-
- esutils@1.1.6: {}
-
esutils@2.0.3: {}
- etag@1.5.1:
- dependencies:
- crc: 3.2.1
-
etag@1.8.1: {}
eval@0.1.8:
@@ -20227,11 +19095,6 @@ snapshots:
'@types/node': 20.19.17
require-like: 0.1.2
- event-emitter@0.3.5:
- dependencies:
- d: 1.0.2
- es5-ext: 0.10.64
-
event-source-polyfill@1.0.31: {}
event-target-shim@5.0.1: {}
@@ -20308,20 +19171,6 @@ snapshots:
dependencies:
express: 5.1.0
- express-session@1.9.3:
- dependencies:
- cookie: 0.1.2
- cookie-signature: 1.0.5
- crc: 3.2.1
- debug: 2.1.3
- depd: 1.0.1
- on-headers: 1.0.2
- parseurl: 1.3.3
- uid-safe: 1.0.1
- utils-merge: 1.0.0
- transitivePeerDependencies:
- - supports-color
-
express@5.1.0:
dependencies:
accepts: 2.0.0
@@ -20356,10 +19205,6 @@ snapshots:
exsolve@1.0.8: {}
- ext@1.7.0:
- dependencies:
- type: 2.7.3
-
extend-shallow@2.0.1:
dependencies:
is-extendable: 0.1.1
@@ -20398,8 +19243,6 @@ snapshots:
fast-json-stable-stringify@2.1.0: {}
- fast-levenshtein@1.0.7: {}
-
fast-levenshtein@2.0.6: {}
fastq@1.19.1:
@@ -20434,28 +19277,10 @@ snapshots:
dependencies:
minimatch: 5.1.6
- fileset@0.1.8:
- dependencies:
- glob: 3.2.11
- minimatch: 0.4.0
-
- fileset@0.2.1:
- dependencies:
- glob: 5.0.15
- minimatch: 2.0.10
-
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
- finalhandler@0.3.2:
- dependencies:
- debug: 2.1.3
- escape-html: 1.0.1
- on-finished: 2.1.1
- transitivePeerDependencies:
- - supports-color
-
finalhandler@2.1.0:
dependencies:
debug: 4.4.3(supports-color@5.5.0)
@@ -20562,8 +19387,6 @@ snapshots:
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
- fresh@0.2.4: {}
-
fresh@2.0.0: {}
from2@2.3.0:
@@ -20619,7 +19442,7 @@ snapshots:
functions-have-names@1.2.3: {}
- generaltranslation@8.1.9:
+ generaltranslation@8.1.14:
dependencies:
'@formatjs/icu-messageformat-parser': 2.11.4
crypto-js: 4.2.0
@@ -20729,17 +19552,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- ggit@0.0.5:
- dependencies:
- check-types: 0.6.5
- cli-table: 0.2.0
- colors: 0.6.2
- commander: 1.1.1
- glob: 3.2.11
- lodash: 1.2.1
- moment: 2.0.0
- optimist: 0.5.2
-
git-config-path@1.0.1:
dependencies:
extend-shallow: 2.0.1
@@ -20810,27 +19622,6 @@ snapshots:
package-json-from-dist: 1.0.1
path-scurry: 2.0.0
- glob@3.2.11:
- dependencies:
- inherits: 2.0.4
- minimatch: 0.3.0
-
- glob@5.0.13:
- dependencies:
- inflight: 1.0.6
- inherits: 2.0.4
- minimatch: 2.0.10
- once: 1.4.0
- path-is-absolute: 1.0.1
-
- glob@5.0.15:
- dependencies:
- inflight: 1.0.6
- inherits: 2.0.4
- minimatch: 3.1.2
- once: 1.4.0
- path-is-absolute: 1.0.1
-
glob@7.2.3:
dependencies:
fs.realpath: 1.0.0
@@ -20889,21 +19680,6 @@ snapshots:
gopd@1.2.0: {}
- got@3.3.1:
- dependencies:
- '@types/keyv': 3.1.4
- '@types/responselike': 1.0.3
- duplexify: 3.7.1
- infinity-agent: 2.0.3
- is-redirect: 1.0.0
- is-stream: 1.1.0
- lowercase-keys: 1.0.1
- nested-error-stacks: 1.0.2
- object-assign: 3.0.0
- prepend-http: 1.0.4
- read-all-stream: 3.1.0
- timed-out: 2.0.0
-
graceful-fs@4.2.10: {}
graceful-fs@4.2.11: {}
@@ -20920,81 +19696,83 @@ snapshots:
groq@4.10.1: {}
- gt-i18n@0.3.7:
+ gt-i18n@0.4.2:
dependencies:
- '@generaltranslation/supported-locales': 2.0.40
- generaltranslation: 8.1.9
+ '@generaltranslation/supported-locales': 2.0.47
+ generaltranslation: 8.1.14
- gt-next@6.12.11(next@16.1.6(@babel/core@7.28.6)(@playwright/test@1.57.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
+ gt-next@6.13.5(next@16.1.6(@babel/core@7.28.6)(@playwright/test@1.57.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
- '@generaltranslation/compiler': 1.1.18
+ '@generaltranslation/compiler': 1.1.25
'@generaltranslation/next-internal': link:packages/next-internal
- '@generaltranslation/supported-locales': 2.0.40
- generaltranslation: 8.1.9
- gt-i18n: 0.3.7
- gt-react: 10.10.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@generaltranslation/supported-locales': 2.0.47
+ generaltranslation: 8.1.14
+ gt-i18n: 0.4.2
+ gt-react: 10.11.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
next: 16.1.6(@babel/core@7.28.6)(@playwright/test@1.57.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
transitivePeerDependencies:
- supports-color
- gt-react@10.10.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
+ gt-react@10.11.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
- '@generaltranslation/react-core': 1.4.7(react@19.2.3)
- '@generaltranslation/supported-locales': 2.0.40
- generaltranslation: 8.1.9
+ '@generaltranslation/react-core': 1.5.4(react@19.2.3)
+ '@generaltranslation/supported-locales': 2.0.47
+ generaltranslation: 8.1.14
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
- gt@0.10.0:
- dependencies:
- allong.es: 0.14.0
- amdefine: 1.0.0
- async: 1.5.0
- check-types: 1.4.0
- cli-color: 1.1.0
- coffee-script: 1.6.3
- console.json: 0.2.1
- custom-logger: 0.3.1
- glob: 5.0.15
- istanbul: 0.4.0
- lazy-ass: 1.0.0
- lodash: 3.10.1
- nodewatch: 0.3.2
- optimist: 0.6.1
- optional-color-logger: 0.0.6
- q: 1.4.1
- quote: 0.4.0
- sprintf: 0.1.5
- untested: 0.1.5
- update-notifier: 0.5.0
+ gt-remark@1.0.5:
+ dependencies:
+ mdast-util-find-and-replace: 3.0.2
+ mdast-util-gfm-footnote: 2.1.0
+ mdast-util-gfm-strikethrough: 2.0.0
+ mdast-util-gfm-table: 2.0.0
+ mdast-util-gfm-task-list-item: 2.0.0
+ micromark-extension-gfm-footnote: 2.1.0
+ micromark-extension-gfm-strikethrough: 2.1.0
+ micromark-extension-gfm-table: 2.1.1
+ micromark-extension-gfm-task-list-item: 2.1.0
transitivePeerDependencies:
- supports-color
- gt@0.8.49:
- dependencies:
- allong.es: 0.14.0
- amdefine: 0.1.1
- async: 1.3.0
- check-types: 1.4.0
- cli-color: 1.0.0
- coffee-script: 1.6.3
- console.json: 0.2.1
- custom-logger: 0.3.1
- glob: 5.0.13
- istanbul: 0.3.17
- lazy-ass: 0.5.8
- lodash: 3.10.0
- nodewatch: 0.3.2
- optimist: 0.6.1
- optional-color-logger: 0.0.6
- q: 1.4.1
- quote: 0.4.0
- sprintf: 0.1.5
- untested: 0.1.5
- update-notifier: 0.5.0
+ gt@2.7.1(@babel/core@7.28.6):
+ dependencies:
+ '@babel/generator': 7.28.6
+ '@babel/parser': 7.28.6
+ '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.6)
+ '@babel/traverse': 7.28.6
+ '@clack/prompts': 1.0.0-alpha.6
+ '@formatjs/icu-messageformat-parser': 2.11.4
+ chalk: 5.6.2
+ commander: 12.1.0
+ dotenv: 16.6.1
+ enhanced-resolve: 5.18.3
+ esbuild: 0.27.2
+ fast-glob: 3.3.3
+ fast-json-stable-stringify: 2.1.0
+ generaltranslation: 8.1.14
+ gt-remark: 1.0.5
+ html-entities: 2.6.0
+ json-pointer: 0.6.2
+ jsonpath-plus: 10.3.0
+ jsonpointer: 5.0.1
+ mdast-util-find-and-replace: 3.0.2
+ micromatch: 4.0.8
+ open: 10.2.0
+ pino: 10.1.0
+ remark-frontmatter: 5.0.0
+ remark-mdx: 3.1.1
+ remark-parse: 11.0.0
+ remark-stringify: 11.0.0
+ resolve: 1.22.11
+ tsconfig-paths: 4.2.0
+ unified: 11.0.5
+ unist-util-visit: 5.0.0
+ yaml: 2.8.1
transitivePeerDependencies:
+ - '@babel/core'
- supports-color
gunzip-maybe@1.4.2:
@@ -21011,38 +19789,10 @@ snapshots:
rou3: 0.7.12
srvx: 0.11.4
- handlebars@1.3.0:
- dependencies:
- optimist: 0.3.7
- optionalDependencies:
- uglify-js: 2.3.6
-
- handlebars@3.0.0:
- dependencies:
- optimist: 0.6.1
- source-map: 0.1.43
- optionalDependencies:
- uglify-js: 2.3.6
-
- handlebars@4.7.8:
- dependencies:
- minimist: 1.2.8
- neo-async: 2.6.2
- source-map: 0.6.1
- wordwrap: 1.0.0
- optionalDependencies:
- uglify-js: 3.19.3
-
hard-rejection@2.1.0: {}
- has-ansi@2.0.0:
- dependencies:
- ansi-regex: 2.1.1
-
has-bigints@1.1.0: {}
- has-flag@1.0.0: {}
-
has-flag@3.0.0: {}
has-flag@4.0.0: {}
@@ -21136,11 +19886,6 @@ snapshots:
domutils: 3.2.2
entities: 7.0.1
- http-errors@1.2.8:
- dependencies:
- inherits: 2.0.4
- statuses: 1.5.0
-
http-errors@2.0.0:
dependencies:
depd: 2.0.0
@@ -21179,8 +19924,6 @@ snapshots:
dependencies:
safer-buffer: 2.1.2
- iconv-lite@0.4.5: {}
-
iconv-lite@0.6.3:
dependencies:
safer-buffer: 2.1.2
@@ -21232,8 +19975,6 @@ snapshots:
indent-string@4.0.0: {}
- infinity-agent@2.0.3: {}
-
inflight@1.0.6:
dependencies:
once: 1.4.0
@@ -21375,8 +20116,6 @@ snapshots:
dependencies:
call-bound: 1.0.4
- is-finite@1.1.0: {}
-
is-fullwidth-code-point@3.0.0: {}
is-generator-function@1.1.0:
@@ -21414,8 +20153,6 @@ snapshots:
is-negative-zero@2.0.3: {}
- is-npm@1.0.0: {}
-
is-number-object@1.1.1:
dependencies:
call-bound: 1.0.4
@@ -21441,8 +20178,6 @@ snapshots:
is-promise@4.0.0: {}
- is-redirect@1.0.0: {}
-
is-reference@1.2.1:
dependencies:
'@types/estree': 1.0.8
@@ -21466,8 +20201,6 @@ snapshots:
dependencies:
protocols: 2.0.2
- is-stream@1.1.0: {}
-
is-stream@2.0.1: {}
is-stream@3.0.0: {}
@@ -21568,56 +20301,6 @@ snapshots:
html-escaper: 2.0.2
istanbul-lib-report: 3.0.1
- istanbul@0.3.17:
- dependencies:
- abbrev: 1.0.9
- async: 1.5.0
- escodegen: 1.6.1
- esprima: 2.4.1
- fileset: 0.2.1
- handlebars: 3.0.0
- js-yaml: 3.14.1
- mkdirp: 0.5.6
- nopt: 3.0.6
- once: 1.4.0
- resolve: 1.1.7
- supports-color: 1.3.1
- which: 1.0.9
- wordwrap: 0.0.3
-
- istanbul@0.3.5:
- dependencies:
- abbrev: 1.0.9
- async: 0.9.2
- escodegen: 1.3.3
- esprima: 1.2.5
- fileset: 0.1.8
- handlebars: 1.3.0
- js-yaml: 3.14.1
- mkdirp: 0.5.6
- nopt: 3.0.6
- once: 1.4.0
- resolve: 0.7.4
- which: 1.0.9
- wordwrap: 0.0.3
-
- istanbul@0.4.0:
- dependencies:
- abbrev: 1.0.9
- async: 1.5.0
- escodegen: 1.7.1
- esprima: 2.5.0
- fileset: 0.2.1
- handlebars: 4.7.8
- js-yaml: 3.14.1
- mkdirp: 0.5.6
- nopt: 3.0.6
- once: 1.4.0
- resolve: 1.1.7
- supports-color: 3.2.3
- which: 1.3.1
- wordwrap: 1.0.0
-
iterator.prototype@1.1.5:
dependencies:
define-data-property: 1.1.4
@@ -21849,8 +20532,6 @@ snapshots:
just-clone@6.2.0: {}
- keypress@0.1.0: {}
-
keyv@4.5.4:
dependencies:
json-buffer: 3.0.1
@@ -21867,25 +20548,6 @@ snapshots:
dependencies:
language-subtag-registry: 0.3.23
- lasso-node@0.1.13:
- dependencies:
- connect: 2.27.6
- istanbul: 0.3.5
- lodash: 2.4.1
- optimist: 0.6.1
- regexp-quote: 0.0.0
- untested: 0.1.5
- transitivePeerDependencies:
- - supports-color
-
- latest-version@1.0.1:
- dependencies:
- package-json: 1.2.0
-
- lazy-ass@0.5.8: {}
-
- lazy-ass@1.0.0: {}
-
lazystream@1.0.1:
dependencies:
readable-stream: 2.3.8
@@ -21935,11 +20597,6 @@ snapshots:
leven@3.1.0: {}
- levn@0.2.5:
- dependencies:
- prelude-ls: 1.1.2
- type-check: 0.3.2
-
levn@0.4.1:
dependencies:
prelude-ls: 1.2.1
@@ -22041,16 +20698,6 @@ snapshots:
lodash.uniq@4.5.0: {}
- lodash@1.2.1: {}
-
- lodash@2.2.1: {}
-
- lodash@2.4.1: {}
-
- lodash@3.10.0: {}
-
- lodash@3.10.1: {}
-
lodash@4.17.21: {}
log-symbols@2.2.0:
@@ -22079,14 +20726,10 @@ snapshots:
loupe@3.2.1: {}
- lowercase-keys@1.0.1: {}
-
lru-cache@10.4.3: {}
lru-cache@11.2.2: {}
- lru-cache@2.7.3: {}
-
lru-cache@5.1.1:
dependencies:
yallist: 3.1.1
@@ -22095,10 +20738,6 @@ snapshots:
dependencies:
yallist: 4.0.0
- lru-queue@0.1.0:
- dependencies:
- es5-ext: 0.10.64
-
lucide-react@0.447.0(react@18.3.1):
dependencies:
react: 18.3.1
@@ -22305,20 +20944,8 @@ snapshots:
media-tracks@0.3.3: {}
- media-typer@0.3.0: {}
-
media-typer@1.1.0: {}
- memoizee@0.3.10:
- dependencies:
- d: 0.1.1
- es5-ext: 0.10.64
- es6-weak-map: 0.1.4
- event-emitter: 0.3.5
- lru-queue: 0.1.0
- next-tick: 0.2.2
- timers-ext: 0.1.8
-
mendoza@3.0.8: {}
meow@9.0.0:
@@ -22342,17 +20969,6 @@ snapshots:
merge2@1.4.1: {}
- method-override@2.3.10:
- dependencies:
- debug: 2.6.9
- methods: 1.1.2
- parseurl: 1.3.3
- vary: 1.1.2
- transitivePeerDependencies:
- - supports-color
-
- methods@1.1.2: {}
-
micromark-core-commonmark@2.0.3:
dependencies:
decode-named-character-reference: 1.2.0
@@ -22607,18 +21223,12 @@ snapshots:
braces: 3.0.3
picomatch: 2.3.1
- mime-db@1.12.0: {}
-
mime-db@1.33.0: {}
mime-db@1.52.0: {}
mime-db@1.54.0: {}
- mime-types@2.0.14:
- dependencies:
- mime-db: 1.12.0
-
mime-types@2.1.18:
dependencies:
mime-db: 1.33.0
@@ -22631,8 +21241,6 @@ snapshots:
dependencies:
mime-db: 1.54.0
- mime@1.2.11: {}
-
mimic-fn@2.1.0: {}
mimic-fn@4.0.0: {}
@@ -22647,24 +21255,10 @@ snapshots:
min-indent@1.0.1: {}
- minimatch@0.3.0:
- dependencies:
- lru-cache: 2.7.3
- sigmund: 1.0.1
-
- minimatch@0.4.0:
- dependencies:
- lru-cache: 2.7.3
- sigmund: 1.0.1
-
minimatch@10.0.3:
dependencies:
'@isaacs/brace-expansion': 5.0.0
- minimatch@2.0.10:
- dependencies:
- brace-expansion: 1.1.12
-
minimatch@3.0.8:
dependencies:
brace-expansion: 1.1.12
@@ -22691,8 +21285,6 @@ snapshots:
is-plain-obj: 1.1.0
kind-of: 6.0.3
- minimist@0.0.10: {}
-
minimist@1.2.8: {}
minipass@4.2.8: {}
@@ -22718,10 +21310,6 @@ snapshots:
mkdirp-classic@0.5.3: {}
- mkdirp@0.5.6:
- dependencies:
- minimist: 1.2.8
-
mkdirp@3.0.1: {}
mlly@1.8.0:
@@ -22735,17 +21323,6 @@ snapshots:
module-alias@2.2.3: {}
- moment@2.0.0: {}
-
- morgan@1.4.1:
- dependencies:
- basic-auth: 1.0.0
- debug: 2.1.3
- depd: 1.0.1
- on-finished: 2.1.1
- transitivePeerDependencies:
- - supports-color
-
motion-dom@12.23.21:
dependencies:
motion-utils: 12.23.6
@@ -22754,19 +21331,10 @@ snapshots:
mri@1.2.0: {}
- ms@0.6.2: {}
-
- ms@0.7.0: {}
-
ms@2.0.0: {}
ms@2.1.3: {}
- multiparty@3.3.2:
- dependencies:
- readable-stream: 1.1.14
- stream-counter: 0.2.0
-
mute-stream@0.0.8: {}
mute-stream@2.0.0: {}
@@ -22775,12 +21343,6 @@ snapshots:
mux-embed@5.9.0: {}
- mz@1.3.0:
- dependencies:
- native-or-bluebird: 1.2.0
- thenify: 3.3.1
- thenify-all: 1.6.0
-
nano-pubsub@3.0.0: {}
nanoid@3.3.11: {}
@@ -22789,26 +21351,10 @@ snapshots:
napi-postinstall@0.3.3: {}
- native-or-bluebird@1.1.2: {}
-
- native-or-bluebird@1.2.0: {}
-
natural-compare@1.4.0: {}
- negotiator@0.4.9: {}
-
negotiator@1.0.0: {}
- neo-async@2.6.2: {}
-
- nested-error-stacks@1.0.2:
- dependencies:
- inherits: 2.0.4
-
- next-tick@0.2.2: {}
-
- next-tick@1.1.0: {}
-
next@15.2.3(@babel/core@7.28.6)(@playwright/test@1.57.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
'@next/env': 15.2.3
@@ -22957,12 +21503,13 @@ snapshots:
- '@babel/core'
- babel-plugin-macros
+ node-addon-api@8.6.0: {}
+
node-fetch@2.7.0:
dependencies:
whatwg-url: 5.0.0
- node-gyp-build@4.8.4:
- optional: true
+ node-gyp-build@4.8.4: {}
node-html-parser@6.1.13:
dependencies:
@@ -22984,12 +21531,6 @@ snapshots:
touch: 3.1.1
undefsafe: 2.0.5
- nodewatch@0.3.2: {}
-
- nopt@3.0.6:
- dependencies:
- abbrev: 1.0.9
-
normalize-package-data@2.5.0:
dependencies:
hosted-git-info: 2.8.9
@@ -23043,8 +21584,6 @@ snapshots:
nwsapi@2.2.22: {}
- object-assign@3.0.0: {}
-
object-assign@4.1.1: {}
object-inspect@1.13.4: {}
@@ -23093,16 +21632,10 @@ snapshots:
on-exit-leak-free@2.1.2: {}
- on-finished@2.1.1:
- dependencies:
- ee-first: 1.1.0
-
on-finished@2.4.1:
dependencies:
ee-first: 1.1.1
- on-headers@1.0.2: {}
-
once@1.4.0:
dependencies:
wrappy: 1.0.2
@@ -23134,32 +21667,6 @@ snapshots:
is-docker: 2.2.1
is-wsl: 2.2.0
- optimist@0.3.7:
- dependencies:
- wordwrap: 0.0.3
-
- optimist@0.5.2:
- dependencies:
- wordwrap: 0.0.3
-
- optimist@0.6.1:
- dependencies:
- minimist: 0.0.10
- wordwrap: 0.0.3
-
- optional-color-logger@0.0.6:
- dependencies:
- custom-logger: 0.2.1
-
- optionator@0.5.0:
- dependencies:
- deep-is: 0.1.4
- fast-levenshtein: 1.0.7
- levn: 0.2.5
- prelude-ls: 1.1.2
- type-check: 0.3.2
- wordwrap: 0.0.3
-
optionator@0.9.4:
dependencies:
deep-is: 0.1.4
@@ -23197,11 +21704,6 @@ snapshots:
os-tmpdir@1.0.2: {}
- osenv@0.1.5:
- dependencies:
- os-homedir: 1.0.2
- os-tmpdir: 1.0.2
-
outdent@0.5.0: {}
outdent@0.8.0: {}
@@ -23293,11 +21795,6 @@ snapshots:
package-json-from-dist@1.0.1: {}
- package-json@1.2.0:
- dependencies:
- got: 3.3.1
- registry-url: 3.1.0
-
package-manager-detector@0.2.11:
dependencies:
quansync: 0.2.11
@@ -23351,7 +21848,7 @@ snapshots:
parse-json@5.2.0:
dependencies:
- '@babel/code-frame': 7.27.1
+ '@babel/code-frame': 7.28.6
error-ex: 1.3.4
json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.2.4
@@ -23428,8 +21925,6 @@ snapshots:
pathval@2.0.1: {}
- pause@0.0.1: {}
-
peek-stream@1.1.3:
dependencies:
buffer-from: 1.1.2
@@ -23448,12 +21943,6 @@ snapshots:
pify@5.0.0: {}
- pinkie-promise@2.0.1:
- dependencies:
- pinkie: 2.0.4
-
- pinkie@2.0.4: {}
-
pino-abstract-transport@2.0.0:
dependencies:
split2: 4.2.0
@@ -23769,12 +22258,8 @@ snapshots:
find-yarn-workspace-root2: 1.2.16
which-pm: 3.0.1
- prelude-ls@1.1.2: {}
-
prelude-ls@1.2.1: {}
- prepend-http@1.0.4: {}
-
prettier-linter-helpers@1.0.0:
dependencies:
fast-diff: 1.3.0
@@ -23821,10 +22306,6 @@ snapshots:
promise.series@0.2.0: {}
- promise@8.3.0:
- dependencies:
- asap: 2.0.6
-
prompts@2.4.2:
dependencies:
kleur: 3.0.3
@@ -23875,10 +22356,6 @@ snapshots:
punycode@2.3.1: {}
- q@1.4.1: {}
-
- qs@2.3.3: {}
-
qs@6.14.0:
dependencies:
side-channel: 1.1.0
@@ -23895,8 +22372,6 @@ snapshots:
quick-lru@5.1.1: {}
- quote@0.4.0: {}
-
raf@3.4.1:
dependencies:
performance-now: 2.1.0
@@ -23905,17 +22380,10 @@ snapshots:
dependencies:
safe-buffer: 5.2.1
- range-parser@1.0.3: {}
-
range-parser@1.2.0: {}
range-parser@1.2.1: {}
- raw-body@1.3.1:
- dependencies:
- bytes: 1.0.0
- iconv-lite: 0.4.5
-
raw-body@3.0.1:
dependencies:
bytes: 3.1.2
@@ -24065,11 +22533,6 @@ snapshots:
react@19.2.3: {}
- read-all-stream@3.1.0:
- dependencies:
- pinkie-promise: 2.0.1
- readable-stream: 2.3.8
-
read-pkg-up@7.0.1:
dependencies:
find-up: 4.1.0
@@ -24188,8 +22651,6 @@ snapshots:
regenerate@1.4.2: {}
- regexp-quote@0.0.0: {}
-
regexp.prototype.flags@1.5.4:
dependencies:
call-bind: 1.0.8
@@ -24212,10 +22673,6 @@ snapshots:
dependencies:
'@pnpm/npm-conf': 2.3.1
- registry-url@3.1.0:
- dependencies:
- rc: 1.2.8
-
registry-url@5.1.0:
dependencies:
rc: 1.2.8
@@ -24257,10 +22714,6 @@ snapshots:
mdast-util-to-markdown: 2.1.2
unified: 11.0.5
- repeating@1.1.3:
- dependencies:
- is-finite: 1.1.0
-
require-directory@2.1.1: {}
require-from-string@2.0.2: {}
@@ -24279,10 +22732,6 @@ snapshots:
resolve.exports@2.0.3: {}
- resolve@0.7.4: {}
-
- resolve@1.1.7: {}
-
resolve@1.22.10:
dependencies:
is-core-module: 2.16.1
@@ -24301,11 +22750,6 @@ snapshots:
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
- response-time@2.2.0:
- dependencies:
- depd: 1.0.1
- on-headers: 1.0.2
-
restore-cursor@3.1.0:
dependencies:
onetime: 5.1.2
@@ -24335,12 +22779,10 @@ snapshots:
glob: 11.0.3
package-json-from-dist: 1.0.1
- rndm@1.1.1: {}
-
rolldown-plugin-dts@0.16.8(rolldown@1.0.0-beta.40)(typescript@5.9.2):
dependencies:
- '@babel/generator': 7.28.3
- '@babel/parser': 7.28.4
+ '@babel/generator': 7.28.6
+ '@babel/parser': 7.28.6
'@babel/types': 7.28.4
ast-kit: 2.1.2
birpc: 2.6.1
@@ -24741,18 +23183,12 @@ snapshots:
scheduler@0.27.0: {}
- scmp@1.0.0: {}
-
scroll-into-view-if-needed@3.1.0:
dependencies:
compute-scroll-into-view: 3.1.1
scrollmirror@1.2.4: {}
- semver-diff@2.1.0:
- dependencies:
- semver: 5.7.2
-
semver@5.7.2: {}
semver@6.3.1: {}
@@ -24765,21 +23201,6 @@ snapshots:
semver@7.7.3: {}
- send@0.10.1:
- dependencies:
- debug: 2.1.3
- depd: 1.0.1
- destroy: 1.0.3
- escape-html: 1.0.1
- etag: 1.5.1
- fresh: 0.2.4
- mime: 1.2.11
- ms: 0.6.2
- on-finished: 2.1.1
- range-parser: 1.0.3
- transitivePeerDependencies:
- - supports-color
-
send@1.2.0:
dependencies:
debug: 4.4.3(supports-color@5.5.0)
@@ -24806,12 +23227,6 @@ snapshots:
seroval@1.5.0: {}
- serve-favicon@2.1.7:
- dependencies:
- etag: 1.5.1
- fresh: 0.2.4
- ms: 0.6.2
-
serve-handler@6.1.6:
dependencies:
bytes: 3.0.0
@@ -24822,26 +23237,6 @@ snapshots:
path-to-regexp: 3.3.0
range-parser: 1.2.0
- serve-index@1.5.3:
- dependencies:
- accepts: 1.1.4
- batch: 0.5.1
- debug: 2.1.3
- http-errors: 1.2.8
- mime-types: 2.0.14
- parseurl: 1.3.3
- transitivePeerDependencies:
- - supports-color
-
- serve-static@1.7.2:
- dependencies:
- escape-html: 1.0.1
- parseurl: 1.3.3
- send: 0.10.1
- utils-merge: 1.0.0
- transitivePeerDependencies:
- - supports-color
-
serve-static@2.2.0:
dependencies:
encodeurl: 2.0.0
@@ -24986,8 +23381,6 @@ snapshots:
siginfo@2.0.0: {}
- sigmund@1.0.1: {}
-
signal-exit@3.0.7: {}
signal-exit@4.1.0: {}
@@ -25038,10 +23431,10 @@ snapshots:
immer: 10.1.3
tiny-warning: 1.0.3
- slide@1.1.6: {}
-
smob@1.5.0: {}
+ smol-toml@1.6.0: {}
+
sonic-boom@4.2.0:
dependencies:
atomic-sleep: 1.0.0
@@ -25065,15 +23458,6 @@ snapshots:
buffer-from: 1.1.2
source-map: 0.6.1
- source-map@0.1.43:
- dependencies:
- amdefine: 1.0.0
-
- source-map@0.2.0:
- dependencies:
- amdefine: 1.0.0
- optional: true
-
source-map@0.6.1: {}
source-map@0.7.6: {}
@@ -25118,8 +23502,6 @@ snapshots:
sprintf-js@1.0.3: {}
- sprintf@0.1.5: {}
-
srvx@0.11.4: {}
stable-hash@0.0.5: {}
@@ -25132,8 +23514,6 @@ snapshots:
stackback@0.0.2: {}
- statuses@1.5.0: {}
-
statuses@2.0.1: {}
statuses@2.0.2: {}
@@ -25147,10 +23527,6 @@ snapshots:
es-errors: 1.3.0
internal-slot: 1.1.0
- stream-counter@0.2.0:
- dependencies:
- readable-stream: 1.1.14
-
stream-each@1.2.3:
dependencies:
end-of-stream: 1.4.5
@@ -25172,10 +23548,6 @@ snapshots:
string-hash@1.1.3: {}
- string-length@1.0.1:
- dependencies:
- strip-ansi: 3.0.1
-
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
@@ -25258,10 +23630,6 @@ snapshots:
character-entities-html4: 2.1.0
character-entities-legacy: 3.0.0
- strip-ansi@3.0.1:
- dependencies:
- ansi-regex: 2.1.1
-
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
@@ -25344,14 +23712,6 @@ snapshots:
stylis@4.3.2: {}
- supports-color@1.3.1: {}
-
- supports-color@2.0.0: {}
-
- supports-color@3.2.3:
- dependencies:
- has-flag: 1.0.0
-
supports-color@5.5.0:
dependencies:
has-flag: 3.0.0
@@ -25446,14 +23806,6 @@ snapshots:
text-table@0.2.0: {}
- thenify-all@1.6.0:
- dependencies:
- thenify: 3.3.1
-
- thenify@3.3.1:
- dependencies:
- any-promise: 1.3.0
-
thread-stream@3.1.0:
dependencies:
real-require: 0.2.0
@@ -25474,13 +23826,6 @@ snapshots:
through@2.3.8: {}
- timed-out@2.0.0: {}
-
- timers-ext@0.1.8:
- dependencies:
- es5-ext: 0.10.64
- next-tick: 1.1.0
-
tiny-invariant@1.3.1: {}
tiny-invariant@1.3.3: {}
@@ -25561,6 +23906,11 @@ snapshots:
tree-kill@1.2.2: {}
+ tree-sitter-python@0.25.0:
+ dependencies:
+ node-addon-api: 8.6.0
+ node-gyp-build: 4.8.4
+
treeify@1.1.0: {}
trim-newlines@3.0.1: {}
@@ -25675,10 +24025,6 @@ snapshots:
turbo-windows-64: 2.5.8
turbo-windows-arm64: 2.5.8
- type-check@0.3.2:
- dependencies:
- prelude-ls: 1.1.2
-
type-check@0.4.0:
dependencies:
prelude-ls: 1.2.1
@@ -25695,19 +24041,12 @@ snapshots:
type-fest@0.8.1: {}
- type-is@1.5.7:
- dependencies:
- media-typer: 0.3.0
- mime-types: 2.0.14
-
type-is@2.0.1:
dependencies:
content-type: 1.0.5
media-typer: 1.1.0
mime-types: 3.0.1
- type@2.7.3: {}
-
typed-array-buffer@1.0.3:
dependencies:
call-bound: 1.0.4
@@ -25770,26 +24109,6 @@ snapshots:
ufo@1.6.1: {}
- uglify-js@2.3.6:
- dependencies:
- async: 0.2.10
- optimist: 0.3.7
- source-map: 0.1.43
- optional: true
-
- uglify-js@3.19.3:
- optional: true
-
- uid-safe@1.0.1:
- dependencies:
- base64-url: 1.3.3
- mz: 1.3.0
-
- uid-safe@1.1.0:
- dependencies:
- base64-url: 1.2.1
- native-or-bluebird: 1.1.2
-
unbox-primitive@1.1.0:
dependencies:
call-bound: 1.0.4
@@ -25924,33 +24243,12 @@ snapshots:
'@unrs/resolver-binding-win32-ia32-msvc': 1.11.1
'@unrs/resolver-binding-win32-x64-msvc': 1.11.1
- untested@0.1.5:
- dependencies:
- check-types: 0.6.5
- ggit: 0.0.5
- gt: 0.8.49
- lasso-node: 0.1.13
- lodash: 2.2.1
- optimist: 0.6.1
- transitivePeerDependencies:
- - supports-color
-
update-browserslist-db@1.1.3(browserslist@4.26.2):
dependencies:
browserslist: 4.26.2
escalade: 3.2.0
picocolors: 1.1.1
- update-notifier@0.5.0:
- dependencies:
- chalk: 1.1.3
- configstore: 1.4.0
- is-npm: 1.0.0
- latest-version: 1.0.1
- repeating: 1.1.3
- semver-diff: 2.1.0
- string-length: 1.0.1
-
uri-js@4.4.1:
dependencies:
punycode: 2.3.1
@@ -26024,14 +24322,10 @@ snapshots:
util-deprecate@1.0.2: {}
- utils-merge@1.0.0: {}
-
uuid@11.1.0: {}
uuid@13.0.0: {}
- uuid@2.0.3: {}
-
uuid@8.3.2: {}
uuidv7@0.4.4: {}
@@ -26047,8 +24341,6 @@ snapshots:
dependencies:
builtins: 5.1.0
- vary@1.0.1: {}
-
vary@1.1.2: {}
vfile-message@4.0.3:
@@ -26061,8 +24353,6 @@ snapshots:
'@types/unist': 3.0.3
vfile-message: 4.0.3
- vhost@3.0.2: {}
-
vite-node@1.6.1(@types/node@20.19.17)(lightningcss@1.30.1)(terser@5.44.0):
dependencies:
cac: 6.7.14
@@ -26575,6 +24865,8 @@ snapshots:
dependencies:
defaults: 1.0.4
+ web-tree-sitter@0.26.6: {}
+
webidl-conversions@3.0.1: {}
webidl-conversions@7.0.0: {}
@@ -26649,12 +24941,6 @@ snapshots:
gopd: 1.2.0
has-tostringtag: 1.0.2
- which@1.0.9: {}
-
- which@1.3.1:
- dependencies:
- isexe: 2.0.0
-
which@2.0.2:
dependencies:
isexe: 2.0.0
@@ -26674,8 +24960,6 @@ snapshots:
word-wrap@1.2.5: {}
- wordwrap@0.0.3: {}
-
wordwrap@1.0.0: {}
wrap-ansi@6.2.0:
@@ -26698,12 +24982,6 @@ snapshots:
wrappy@1.0.2: {}
- write-file-atomic@1.3.4:
- dependencies:
- graceful-fs: 4.2.11
- imurmurhash: 0.1.4
- slide: 1.1.6
-
write-file-atomic@3.0.3:
dependencies:
imurmurhash: 0.1.4
@@ -26719,10 +24997,6 @@ snapshots:
dependencies:
is-wsl: 3.1.0
- xdg-basedir@2.0.0:
- dependencies:
- os-homedir: 1.0.2
-
xdg-basedir@4.0.0: {}
xdg-basedir@5.1.0: {}