glotstack 0.0.6 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,36 +1,41 @@
1
1
  {
2
2
  "name": "glotstack",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "author": "JD Cumpson",
7
7
  "license": "MIT",
8
8
  "private": false,
9
9
  "dependencies": {
10
+ "@eslint/js": "^9.26.0",
10
11
  "commander": "^13.1.0",
11
- "eslint": "^9.26.0"
12
+ "eslint": "^9.26.0",
13
+ "eslint-plugin-i18next": "^6.1.1",
14
+ "eslint-plugin-react": "^7.37.5",
15
+ "form-data": "^4.0.2",
16
+ "undici": "^7.9.0"
12
17
  },
13
18
  "devDependencies": {
14
- "@eslint/js": "^9.26.0",
15
19
  "@types/js-yaml": "^4.0.9",
16
20
  "@types/node": "^22.15.17",
17
- "@types/react": "^19.1.4",
21
+ "@types/react": "^18.3.1",
22
+ "@types/react-dom": "^18.3.1",
18
23
  "globals": "^16.1.0",
19
24
  "js-yaml": "^4.1.0",
25
+ "nodemon": "^3.1.10",
20
26
  "typescript": "5.4.4",
21
27
  "typescript-eslint": "^8.32.1"
22
28
  },
23
29
  "peerDependencies": {
24
- "react": "^19.1.0"
25
- },
26
- "resolutions": {
27
- "react": "18.3.1"
30
+ "react": "^18.3.1",
31
+ "react-dom": "^18.3.1"
28
32
  },
29
33
  "bin": {
30
34
  "glotstack": "dist/cli.js"
31
35
  },
32
36
  "scripts": {
33
37
  "build": "tsc && scripts/fix-shebang.sh dist/cli.js",
38
+ "watch": "nodemon --watch src --ext ts,tsx,mjs,json --exec \"bash -c 'npm run build'\"",
34
39
  "prepublishOnly": "yarn run build",
35
40
  "glotstack": "node dist/cli.js"
36
41
  }
package/src/cli.tsx CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Command, program } from 'commander'
2
2
  import path from 'path'
3
- import { promises as fs, read } from 'fs'
3
+ import { promises as fs, createReadStream } from 'fs'
4
4
  import { findGlotstackConfig } from './util/findConfig'
5
5
  import { cwd } from 'process'
6
6
  import { merge } from './util/object'
@@ -8,36 +8,102 @@ import { loadYaml } from './util/yaml'
8
8
  import eslint from 'eslint'
9
9
  import * as readline from "node:readline/promises"
10
10
  import { stdin as input, stdout as output } from "node:process"
11
+ import { Translations } from 'src'
12
+ import { fetch } from 'undici'
13
+ import { FormData } from 'undici'
14
+ import { openAsBlob } from 'node:fs'
15
+ import { readdir, readFile, writeFile } from 'node:fs/promises'
16
+ import { resolve } from 'node:path'
17
+
18
+ const fetchGlotstack = async function <T>(url: string, apiKey: string, body: Record<string, any> | FormData, overrideHeaders?: Record<string, any>): Promise<T> {
19
+ console.info(`Fetching glotstack.ai: ${url}`)
20
+
21
+ const headers: Record<string, any> = {
22
+ 'authorization': `Bearer ${apiKey}`,
23
+ ...(overrideHeaders == null ? {} : overrideHeaders),
24
+ }
11
25
 
26
+ let payloadBody: FormData | string
12
27
 
13
- const fetchGlotstack = async (apiOrigin: string, apiKey: string, body: object) => {
14
- const url = `${apiOrigin}/api/translations`
15
- console.info(`Fetching translations from: ${url}`)
16
-
17
- const headers = {
18
- 'Content-Type': 'application/json',
19
- 'Authorization': `Bearer ${apiKey}`,
28
+ if (!(body instanceof FormData)) {
29
+ headers['content-type'] = 'application/json'
30
+ payloadBody = JSON.stringify(body)
31
+ } else {
32
+ payloadBody = body
20
33
  }
21
34
 
22
35
  try {
23
- const res = await fetch(url, { method: 'POST', body: JSON.stringify(body), headers })
36
+ const res = await fetch(url, { method: 'POST', body: payloadBody, headers })
24
37
  if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`)
25
- return res.json()
26
- // fs.writeFile(`${outputDir}/source.json`, JSON.stringify(json, null, 2))
38
+ return res.json() as T
27
39
  } catch (err) {
28
40
  console.error('Fetch failed:', err)
29
41
  process.exit(1)
30
42
  }
31
43
  }
32
44
 
45
+ function unflatten(flat: Record<string, any>): Record<string, any> {
46
+ const result: Record<string, any> = {}
47
+
48
+ for (const flatKey in flat) {
49
+ const parts = flatKey.split('.')
50
+ let current = result
51
+
52
+ parts.forEach((part, idx) => {
53
+ if (idx === parts.length - 1) {
54
+ current[part] = flat[flatKey]
55
+ } else {
56
+ if (!(part in current)) {
57
+ current[part] = {}
58
+ }
59
+ current = current[part]
60
+ }
61
+ })
62
+ }
63
+
64
+ return result
65
+ }
66
+
67
+ const DEFAULT_OPTIONS: Record<string, string> = {
68
+ sourcePath: '.',
69
+ sourceLocale: 'en-US',
70
+ apiOrigin: 'https://glotstack.ai'
71
+ }
72
+
73
+ async function resolveConfigAndOptions(options: Record<string, any>) {
74
+
75
+ const config = await findGlotstackConfig(cwd()) ?? {}
76
+
77
+ if ('outputLocales' in options) {
78
+ if ((options.outputLocales as string[]).includes('en-US')) {
79
+ console.warn('en-US detected in outputLocales, removing')
80
+ options.outputLocales = options.outputLocales.filter((x: string) => x !== 'en-US')
81
+ }
82
+ }
83
+
84
+ const resolved = merge({}, DEFAULT_OPTIONS, config, options)
85
+
86
+ // special case to match source
87
+ if (resolved.outputDir == null) {
88
+ resolved.outputDir = resolved.sourcePath
89
+ }
90
+
91
+ return resolved
92
+ }
93
+
33
94
 
34
95
  async function run(args: string[]) {
35
96
  program
36
97
  .command('extract-translations')
37
- .option('--source-path [path]', 'to source files root directory', '.')
38
- .option('--api-origin [url]', 'glotstack api origin', process.env.GLOTSTACK_HOST ?? 'https://glotstack.ai')
39
- .option('--yes', 'skip confirm checks', false)
40
- .action(async (options: Record<string, any>) => {
98
+ .description('extract translations from all compatible source files.')
99
+ .option('--source-path [path]', `path directory containing [locale].json files (default=${DEFAULT_OPTIONS['sourcePath']})`)
100
+ .option('--source-locale [locale]', `the locale you provide "context" in, your primary locale (default=${DEFAULT_OPTIONS['sourceLocale']})`)
101
+ .option('--api-origin [url]', `glotstack api origin (default=${DEFAULT_OPTIONS['apiOrigin']})`)
102
+ .option('--output-dir [path]', 'path to output directory (default=<source-path>')
103
+ .option('--api-key [key]', 'api key for glotstack.ai')
104
+ .option('--yes', 'skip confirm checks')
105
+ .action(async (inputOptions: Record<string, any>) => {
106
+ const options = await resolveConfigAndOptions(inputOptions)
41
107
  if (!options.apiOrigin) {
42
108
  throw new Error('apiOrigin must be specified')
43
109
  }
@@ -49,7 +115,6 @@ async function run(args: string[]) {
49
115
  .map((r) => r.filePath)
50
116
 
51
117
  const rl = readline.createInterface({ input, output })
52
-
53
118
  const askToSend = async (): Promise<boolean> => {
54
119
  if (options.yes) {
55
120
  return true
@@ -67,7 +132,17 @@ async function run(args: string[]) {
67
132
 
68
133
  const send = await askToSend()
69
134
  if (send) {
70
- console.info('Sending files to LLM')
135
+ console.info('Sending files to generate new source and extracted strings')
136
+ const url = `${options.apiOrigin}/uploads/translations/extract`
137
+ const form = new FormData()
138
+
139
+ for (let i = 0; i < filesWithIssues.length; i++) {
140
+ const filePath = filesWithIssues[i]
141
+ form.append(`file_${i}`, await openAsBlob(filePath), filePath)
142
+ console.debug(`Uploading file: ${filePath}`)
143
+ }
144
+ const data = await fetchGlotstack<{ translations: { name: string; modified_source: { url: string } }[] }>(url, options.apiKey, form)
145
+ data.translations.map(elem => console.info(`Source and translations available for: ${elem.name}:\n ${elem.modified_source.url}\n\n`))
71
146
  rl.close()
72
147
  } else {
73
148
  rl.close()
@@ -76,46 +151,27 @@ async function run(args: string[]) {
76
151
 
77
152
  program
78
153
  .command('get-translations')
79
- .option('--source-path [path]', 'path to en-US.json (or your canonical source json)')
80
- .option('--api-origin [url]', 'glotstack api origin', process.env.GLOTSTACK_HOST ?? 'https://glotstack.ai')
81
- .option('--output-dir [path]', 'path to output directory')
154
+ .description('fetch translations for all [output-locals...]. Use .glotstack.json for repeatable results.')
155
+ .option('--source-path [path]', `path directory containing [locale].json files (default=${DEFAULT_OPTIONS['sourcePath']})`)
156
+ .option('--source-locale [locale]', `the locale you provide "context" in, your primary locale (default=${DEFAULT_OPTIONS['sourceLocale']})`)
157
+ .option('--api-origin [url]', `glotstack api origin (default=${DEFAULT_OPTIONS['apiOrigin']})`)
158
+ .option('--output-dir [path]', 'path to output directory (default=<source-path>')
82
159
  .option('--api-key [key]', 'api key for glotstack.ai')
83
160
  .option('--project-id [id]', '(optional) specific project to use')
84
161
  .argument('[output-locales...]', 'locales to get translations for')
85
162
  .action(async (outputLocales: string[], options: Record<string, any>, command: Command) => {
86
- const configPath = findGlotstackConfig(cwd())
87
- let config = {}
88
-
89
- if (configPath != null) {
90
- console.info('Loading config file at ', configPath)
91
- try {
92
- const text = await fs.readFile(configPath, 'utf-8')
93
- config = JSON.parse(text)
94
- console.info('Loaded config file', config)
95
- } catch (err) {
96
- //pass
97
- }
98
- }
99
-
100
- const resolved = merge(config, options, { outputLocales })
101
- const { apiOrigin, sourcePath, outputDir, projectId } = resolved
102
-
103
- if ((resolved.outputLocales as string[]).includes('en-US')) {
104
- console.warn('en-US detected in outputLocales, removing')
105
- resolved.outputLocales = resolved.outputLocales.filter((x: string) => x !== 'en-US')
106
- }
107
-
108
- if (!sourcePath) {
163
+ const resolved = await resolveConfigAndOptions({ ...options, outputLocales: outputLocales })
164
+ if (!resolved.sourcePath) {
109
165
  throw new Error('sourcePath must be specified')
110
166
  }
111
- if (!apiOrigin) {
167
+ if (!resolved.apiOrigin) {
112
168
  throw new Error('apiOrigin must be specified')
113
169
  }
114
- if (!outputDir) {
170
+ if (!resolved.outputDir) {
115
171
  throw new Error('outputDir must be specified')
116
172
  }
117
173
 
118
- const absPath = path.resolve(sourcePath)
174
+ const absPath = path.resolve(resolved.sourcePath, `${resolved.sourceLocale}.json`)
119
175
  const fileContent = await fs.readFile(absPath, 'utf-8')
120
176
 
121
177
  let json = null
@@ -133,18 +189,62 @@ async function run(args: string[]) {
133
189
  const body = {
134
190
  locales: resolved.outputLocales,
135
191
  translations: json,
136
- ...{ ... (projectId != null ? { projectId } : {}) },
192
+ ...{ ... (resolved.projectId != null ? { projectId: resolved.projectId } : {}) },
137
193
  }
138
194
 
139
- const data = await fetchGlotstack(apiOrigin, resolved.apiKey, body)
195
+ const url = `${resolved.apiOrigin}/api/translations`
196
+ const data = await fetchGlotstack<{ data: Translations }>(url, resolved.apiKey, body)
140
197
  console.info('Received translations:', data)
141
198
  Object.entries(data.data).map(([key, val]) => {
142
- const p = `${outputDir}/${key}.json`
199
+ const p = `${resolved.outputDir}/${key}.json`
143
200
  console.info(`Writing file ${p}`)
144
- fs.writeFile(`${outputDir}/${key}.json`, JSON.stringify(val, null, 2))
201
+ fs.writeFile(`${resolved.outputDir}/${key}.json`, JSON.stringify(val, null, 2))
145
202
  })
146
203
  })
147
204
 
205
+ program
206
+ .command('format-json')
207
+ .description('format files in --source-path [path] to nested (not flat)')
208
+ .option('--source-path [path]', `path directory containing [locale].json files (default=${DEFAULT_OPTIONS['sourcePath']})`)
209
+ .option('--yes', 'skip confirm checks')
210
+ .action(async (inputOptions: Record<string, any>) => {
211
+ const options = await resolveConfigAndOptions(inputOptions)
212
+
213
+ if (!options.sourcePath) {
214
+ throw new Error('sourcePath must be specified')
215
+ }
216
+ const rl = readline.createInterface({ input, output })
217
+ const askToSend = async (): Promise<boolean> => {
218
+ if (options.yes) {
219
+ return true
220
+ }
221
+ const response = await rl.question(`This will update your source files -- have you checked them into SCM/git? Type yes to proceed (yes/no):`)
222
+ if (response === 'yes') {
223
+ return true
224
+ } else if (response !== 'no') {
225
+ console.error('Please respond with yes or no.')
226
+ return askToSend()
227
+ } else {
228
+ return false
229
+ }
230
+ }
231
+ const yes = await askToSend()
232
+ if (yes) {
233
+ const files = await readdir(options.sourcePath)
234
+ for (let i = 0; i < (await files).length; i++) {
235
+ const fp = resolve(options.sourcePath, files[i])
236
+ const text = await readFile(fp, 'utf-8')
237
+ const json = JSON.parse(text)
238
+ const formatted = JSON.stringify(unflatten(json), null, 2)
239
+ await writeFile(fp, formatted)
240
+ }
241
+ rl.close()
242
+ }
243
+ rl.close()
244
+
245
+ })
246
+
247
+
148
248
  await program.parseAsync(args)
149
249
  }
150
250
 
package/src/index.tsx CHANGED
@@ -1,15 +1,8 @@
1
1
  import * as React from 'react'
2
- import { merge, isEqual } from './util/object'
2
+ import { merge } from './util/object'
3
+ import { debug } from 'node:console'
3
4
 
4
- export type LocaleRegion =
5
- | 'en'
6
- | 'en-US'
7
- | 'en-US-genz'
8
- | 'fr-FR'
9
- | 'de-DE'
10
- | 'nl-NL'
11
- | 'jp-JP'
12
- | string
5
+ export type LocaleRegion = string
13
6
 
14
7
 
15
8
  export interface TranslationLeaf {
@@ -21,13 +14,15 @@ export interface Translations {
21
14
  [key: string]: Translations | TranslationLeaf
22
15
  }
23
16
 
17
+
24
18
  export interface ContextType {
25
19
  translations: Translations
26
20
  locale: string | null
27
21
  loadTranslations: (locale: LocaleRegion) => Promise<Translations>
28
22
  setLocale: (locale: LocaleRegion) => void
29
- importMethod: (locale: LocaleRegion) => Promise<Translations>
23
+ importMethod: (locale: LocaleRegion) => Promise<Translations>
30
24
  t: (key: string, options?: { locale?: LocaleRegion }) => string
25
+ ssr: boolean
31
26
  }
32
27
 
33
28
  export const GlotstackContext = React.createContext<ContextType>({
@@ -37,18 +32,61 @@ export const GlotstackContext = React.createContext<ContextType>({
37
32
  locale: null,
38
33
  importMethod: (_locale: LocaleRegion) => { throw new Error('import method not set') },
39
34
  t: () => { throw new Error('import method not set') },
35
+ ssr: false
40
36
  })
41
37
 
42
38
  interface GlotstackProviderProps {
43
39
  children: React.ReactNode
44
- initialTranslations?: Translations
40
+ initialTranslations?: Record<string, Translations>
45
41
  initialLocale?: LocaleRegion
46
42
  onTranslationLoaded?: (locale: LocaleRegion, translations: Translations) => void
47
43
  onLocaleChange?: (locale: LocaleRegion) => void
48
- importMethod: (locale: LocaleRegion) => Promise<Translations>
44
+ importMethod: ContextType['importMethod']
45
+ ssr?: boolean
46
+ }
47
+
48
+ export enum LogLevel {
49
+ DEBUG = 0,
50
+ LOG = 1,
51
+ INFO = 2,
52
+ WARNING = 3,
53
+ ERROR = 4,
54
+ }
55
+
56
+ const LogLevelToFunc: Record<LogLevel, (...args: Parameters<typeof console.info>) => void> = {
57
+ [LogLevel.DEBUG]: console.debug,
58
+ [LogLevel.INFO]: console.info,
59
+ [LogLevel.LOG]: console.log,
60
+ [LogLevel.WARNING]: console.warn,
61
+ [LogLevel.ERROR]: console.error,
62
+ } as const
63
+
64
+ let logLevel: LogLevel = LogLevel.DEBUG
65
+
66
+ export const setLogLevel = (level: LogLevel) => {
67
+ logLevel = level
68
+ }
69
+
70
+ const makeLoggingFunction = (level: LogLevel) => (...args: Parameters<typeof console.info>) => {
71
+ const func = LogLevelToFunc[level]
72
+ if (level < logLevel) {
73
+ return
74
+ }
75
+ return func(`[level=${level} logLevel=${logLevel}][glotstack.ai]`, ...args)
76
+ }
77
+
78
+ const logger = {
79
+ debug: makeLoggingFunction(LogLevel.DEBUG),
80
+ info: makeLoggingFunction(LogLevel.INFO),
81
+ warn: makeLoggingFunction(LogLevel.WARNING),
82
+ error: makeLoggingFunction(LogLevel.ERROR),
83
+
49
84
  }
50
85
 
51
86
  export const access = (key: string, locale: LocaleRegion, translations: Translations) => {
87
+ if (translations == null) {
88
+ return key
89
+ }
52
90
  const access = [...key.split('.')] as [LocaleRegion, ...string[]]
53
91
  const localeTranslations = translations?.[locale]
54
92
 
@@ -64,21 +102,29 @@ export const access = (key: string, locale: LocaleRegion, translations: Translat
64
102
  return (value?.value ?? key) as string
65
103
  }
66
104
 
67
- export const GlotstackProvider = ({ children, initialLocale, initialTranslations, onLocaleChange, onTranslationLoaded, importMethod }: GlotstackProviderProps) => {
105
+ export const GlotstackProvider = ({ children, initialLocale, initialTranslations, onLocaleChange, onTranslationLoaded, importMethod, ssr}: GlotstackProviderProps) => {
68
106
  if (initialLocale == null) {
69
107
  throw new Error('initialLocale must be set')
70
108
  }
71
109
  const [locale, setLocale] = React.useState<LocaleRegion>(initialLocale)
72
- const translationsRef = React.useRef(initialTranslations)
110
+ const translationsRef = React.useRef<Record<string, Translations>|null>(initialTranslations || null)
73
111
  const loadingRef = React.useRef<Record<string, Promise<Translations>>>({})
74
112
 
75
- const loadTranslations = React.useCallback(async (locale: string) => {
113
+ const loadTranslations = React.useCallback(async (locale: string, opts?: {force?: boolean}) => {
76
114
  // TODO: if translations are loaded only reload if some condition is
77
115
  try {
78
- if (loadingRef.current?.[locale] != null) {
116
+ if (loadingRef.current?.[locale] != null && opts?.force != true) {
117
+ logger.debug('Waiting for translations already loading', locale)
79
118
  return (await loadingRef.current?.[locale])
80
119
  }
81
- loadingRef.current[locale] = importMethod(locale)
120
+ if (translationsRef.current?.[locale] != null && opts?.force != true) {
121
+ logger.debug('Skipping load for translations', locale, translationsRef.current?.[locale], translationsRef.current)
122
+ return translationsRef.current?.[locale]
123
+ }
124
+ if (loadingRef.current != null) {
125
+ logger.debug('Loading translations', locale, merge({}, translationsRef.current ?? {}))
126
+ loadingRef.current[locale] = importMethod(locale)
127
+ }
82
128
  const result = await loadingRef.current[locale]
83
129
 
84
130
  if (result == null) {
@@ -90,7 +136,7 @@ export const GlotstackProvider = ({ children, initialLocale, initialTranslations
90
136
  onTranslationLoaded?.(locale, result)
91
137
  return result
92
138
  } catch (err) {
93
- console.error('Unable to import translations', err)
139
+ logger.error('Unable to import translations', err)
94
140
  throw err
95
141
  }
96
142
  }, [importMethod, onTranslationLoaded])
@@ -98,7 +144,7 @@ export const GlotstackProvider = ({ children, initialLocale, initialTranslations
98
144
  React.useEffect(() => {
99
145
  const run = async () => {
100
146
  onLocaleChange?.(locale)
101
- const result = await loadTranslations(locale)
147
+ await loadTranslations(locale)
102
148
  }
103
149
  React.startTransition(() => {
104
150
  run()
@@ -113,15 +159,11 @@ export const GlotstackProvider = ({ children, initialLocale, initialTranslations
113
159
  importMethod,
114
160
  loadTranslations,
115
161
  t: (key: string, opts?: { locale?: LocaleRegion }) => {
116
- React.useEffect(() => {
117
- if (opts?.locale == null) {
118
- return
119
- }
120
- loadTranslations(opts?.locale)
121
- }, [locale, opts?.locale])
162
+ const resolvedLocale = opts?.locale ?? locale
163
+ loadTranslations(resolvedLocale)
122
164
  return access(key, opts?.locale ?? locale, translationsRef.current ?? {})
123
- }
124
-
165
+ },
166
+ ssr: ssr == true,
125
167
  }
126
168
  }, [locale, importMethod])
127
169
 
@@ -134,7 +176,6 @@ export const useGlotstack = () => {
134
176
  return React.useContext(GlotstackContext)
135
177
  }
136
178
 
137
-
138
179
  export const useTranslations = (_options?: Record<never, never>) => {
139
180
  const context = React.useContext(GlotstackContext)
140
181
  return context
@@ -1,26 +1,42 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
1
+ import { existsSync } from 'fs'
2
+ import { readFile } from 'fs/promises'
3
+ import * as path from 'path'
3
4
 
4
5
  /**
5
6
  * Recursively looks for `.glotstack.json` from the current directory up to the root.
6
7
  * @param startDir The directory to start the search from. Defaults to process.cwd().
7
8
  * @returns The absolute path to the file if found, or null if not found.
8
9
  */
9
- export function findGlotstackConfig(startDir: string = process.cwd()): string | null {
10
- let currentDir = path.resolve(startDir);
10
+ export async function findGlotstackConfig(startDir: string = process.cwd()): Promise<object | null> {
11
+ let currentDir = path.resolve(startDir)
12
+ let configPath = null
11
13
 
12
14
  while (true) {
13
- const candidate = path.join(currentDir, '.glotstack.json');
14
- if (fs.existsSync(candidate)) {
15
- return candidate;
15
+ const candidate = path.join(currentDir, '.glotstack.json')
16
+ if (existsSync(candidate)) {
17
+ configPath = candidate
16
18
  }
17
19
 
18
- const parentDir = path.dirname(currentDir);
20
+ const parentDir = path.dirname(currentDir)
19
21
  if (parentDir === currentDir) {
20
- break; // Reached root
22
+ break // Reached root
21
23
  }
22
- currentDir = parentDir;
24
+ currentDir = parentDir
23
25
  }
24
26
 
25
- return null;
27
+ let config = {}
28
+
29
+ if (configPath != null) {
30
+ console.info('Loading config file at ', configPath)
31
+ try {
32
+ const text = await readFile(configPath, 'utf-8')
33
+ config = JSON.parse(text)
34
+ console.info('Loaded config file', config)
35
+ return config
36
+ } catch (err) {
37
+ console.warn('Could not load config', configPath)
38
+ }
39
+ }
40
+ console.warn('Could not find any .glotstack.json config files')
41
+ return null
26
42
  }
package/tsconfig.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "module": "CommonJS",
9
9
  "declaration": true,
10
10
  "strict": true,
11
- "jsx": "react",
11
+ "jsx": "react-jsx",
12
12
  "sourceMap": true,
13
13
  "baseUrl": ".",
14
14
  "paths": {