@mono-labs/project 0.0.248

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.
@@ -0,0 +1,83 @@
1
+ // scripts/generate-repo-help.mjs
2
+ // Generates a developer-friendly workspace command reference.
3
+ //
4
+ // Output: docs/workspaces.md
5
+ //
6
+ // Run (from repo root):
7
+ // node ./scripts/generate-repo-help.mjs
8
+ //
9
+ // Philosophy:
10
+ // - Optimize for onboarding and day-to-day use
11
+ // - Keep raw yarn workspace commands for reference
12
+ // - Emphasize `yarn mono` as the primary interface
13
+
14
+ import { promises as fs } from 'node:fs';
15
+ import path from 'node:path';
16
+
17
+ // Type definitions
18
+ export interface GenerateDocsIndexOptions {
19
+ docsDir: string;
20
+ excludeFile?: string;
21
+ }
22
+
23
+ function createSpacer() {
24
+ return '\n\n';
25
+ }
26
+ /**
27
+ * Generate a docs index from markdown files.
28
+ *
29
+ * @param options - Options for docs index generation
30
+ * @returns Markdown-formatted index
31
+ */
32
+ export async function generateDocsIndex({
33
+ docsDir,
34
+ excludeFile,
35
+ }: GenerateDocsIndexOptions): Promise<string> {
36
+ // Always resolve docsDir relative to the working directory
37
+ const dirPath = path.resolve(process.cwd(), docsDir);
38
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
39
+
40
+ const links: string[] = [];
41
+ for (const entry of entries) {
42
+ if (!entry.isFile()) continue;
43
+ if (!entry.name.endsWith('.md')) continue;
44
+
45
+ // Always ignore docs/readme.md (case-insensitive)
46
+ if (entry.name.toLowerCase() === 'readme.md') continue;
47
+
48
+ // Optionally ignore a caller-specified file
49
+ if (excludeFile && entry.name === excludeFile) continue;
50
+
51
+ const filePath = path.join(dirPath, entry.name);
52
+ const contents = await fs.readFile(filePath, 'utf8');
53
+
54
+ // Find first markdown H1
55
+ const match = contents.match(/^#\s+(.+)$/m);
56
+ if (!match) continue;
57
+
58
+ const rawTitle = match[1].trim();
59
+ const relativeLink = `./${entry.name}`;
60
+
61
+ /**
62
+ * Detect leading non-alphanumeric characters (emoji / symbols).
63
+ * This matches one or more Unicode characters that are NOT letters or numbers.
64
+ */
65
+ const leadingSymbolMatch = rawTitle.match(/^([^\p{L}\p{N}]+)\s*(.+)$/u);
66
+
67
+ if (leadingSymbolMatch) {
68
+ const [, symbol, title] = leadingSymbolMatch;
69
+ links.push(`- ${symbol.trim()} [${title.trim()}](${relativeLink})`);
70
+ } else {
71
+ links.push(`- [${rawTitle.trim()}](${relativeLink})`);
72
+ }
73
+ }
74
+
75
+ // Sort alphabetically by rendered text (stable output)
76
+ links.sort((a, b) => a.localeCompare(b));
77
+
78
+ // Append Back to Readme
79
+ links.push('');
80
+ links.push('🏠 ← [Back to README](../README.md)');
81
+
82
+ return ['', '---', '', ...links].join('\n');
83
+ }
@@ -0,0 +1,351 @@
1
+ // scripts/generate-repo-help.mjs
2
+ // Generates a developer-friendly workspace command reference.
3
+ //
4
+ // Output: docs/workspaces.md
5
+ //
6
+ // Run (from repo root):
7
+ // node ./scripts/generate-repo-help.mjs
8
+ //
9
+ // Philosophy:
10
+ // - Optimize for onboarding and day-to-day use
11
+ // - Keep raw yarn workspace commands for reference
12
+ // - Emphasize `yarn mono` as the primary interface
13
+
14
+ import { promises as fs } from 'node:fs'
15
+ import path from 'node:path'
16
+ import { generateDocsIndex } from './generate-docs'
17
+
18
+ interface PackageInfo {
19
+ name: string
20
+ dir: string
21
+ scripts: Record<string, string>
22
+ }
23
+
24
+ // ----------------- config -----------------
25
+ // Always use the working directory as the root for all file actions
26
+ const REPO_ROOT = path.resolve(process.cwd())
27
+ const ROOT_PKG_JSON = path.join(REPO_ROOT, 'package.json')
28
+ const OUTPUT_PATH = path.join(REPO_ROOT, 'docs', 'workspaces.md')
29
+
30
+ // ----------------- helpers -----------------
31
+ async function exists(p: string): Promise<boolean> {
32
+ // Always resolve path relative to working directory
33
+ const absPath = path.resolve(process.cwd(), p)
34
+ try {
35
+ await fs.access(absPath)
36
+ return true
37
+ } catch {
38
+ return false
39
+ }
40
+ }
41
+
42
+ function isObject(v: unknown): v is Record<string, unknown> {
43
+ return v !== null && typeof v === 'object' && !Array.isArray(v)
44
+ }
45
+
46
+ function toPosix(p: string): string {
47
+ return p.split(path.sep).join('/')
48
+ }
49
+
50
+ function mdEscapeInline(s: string): string {
51
+ return String(s ?? '').replaceAll('`', '\`')
52
+ }
53
+
54
+ function slugifyForGithubAnchor(title: string): string {
55
+ return String(title ?? '')
56
+ .trim()
57
+ .toLowerCase()
58
+ .replace(/[^\w\s-]/g, '')
59
+ .replace(/\s+/g, '-')
60
+ .replace(/-+/g, '-')
61
+ }
62
+
63
+ async function readJson<T = any>(filePath: string): Promise<T> {
64
+ // Always resolve filePath relative to working directory
65
+ const absPath = path.resolve(process.cwd(), filePath)
66
+ const raw = await fs.readFile(absPath, 'utf8')
67
+ return JSON.parse(raw)
68
+ }
69
+
70
+ function normalizeWorkspacePatterns(workspacesField: unknown): string[] {
71
+ if (Array.isArray(workspacesField)) return workspacesField as string[]
72
+ if (isObject(workspacesField) && Array.isArray((workspacesField as any).packages))
73
+ return (workspacesField as any).packages
74
+ return []
75
+ }
76
+
77
+ // ----------------- glob expansion -----------------
78
+ function matchSegment(patternSeg: string, name: string): boolean {
79
+ if (patternSeg === '*') return true
80
+ if (!patternSeg.includes('*')) return patternSeg === name
81
+
82
+ const escaped = patternSeg.replace(/[.+?^${}()|[\]\\]/g, '\\$&')
83
+ const regex = new RegExp('^' + escaped.replaceAll('*', '.*') + '$')
84
+ return regex.test(name)
85
+ }
86
+
87
+ async function expandWorkspacePattern(root: string, pattern: string): Promise<string[]> {
88
+ const segs = toPosix(pattern).split('/').filter(Boolean)
89
+
90
+ async function expandFrom(dir: string, segIndex: number): Promise<string[]> {
91
+ // Always resolve dir relative to working directory
92
+ const absDir = path.resolve(process.cwd(), dir)
93
+ if (segIndex >= segs.length) return [absDir]
94
+
95
+ const seg = segs[segIndex]
96
+
97
+ if (seg === '**') {
98
+ const results: string[] = []
99
+ results.push(...(await expandFrom(absDir, segIndex + 1)))
100
+
101
+ const entries = await fs.readdir(absDir, { withFileTypes: true }).catch(() => [])
102
+ for (const e of entries) {
103
+ if (e.isDirectory()) {
104
+ results.push(...(await expandFrom(path.join(absDir, e.name), segIndex)))
105
+ }
106
+ }
107
+ return results
108
+ }
109
+
110
+ const entries = await fs.readdir(absDir, { withFileTypes: true }).catch(() => [])
111
+ const results: string[] = []
112
+ for (const e of entries) {
113
+ if (e.isDirectory() && matchSegment(seg, e.name)) {
114
+ results.push(...(await expandFrom(path.join(absDir, e.name), segIndex + 1)))
115
+ }
116
+ }
117
+ return results
118
+ }
119
+
120
+ return [...new Set(await expandFrom(root, 0))]
121
+ }
122
+
123
+ async function findWorkspaceRoots(
124
+ repoRoot: string,
125
+ workspacePatterns: string[]
126
+ ): Promise<string[]> {
127
+ const roots: string[] = []
128
+ for (const pat of workspacePatterns) {
129
+ const expandedDirs = await expandWorkspacePattern(repoRoot, pat)
130
+ roots.push(...expandedDirs)
131
+ }
132
+ return [...new Set(roots)]
133
+ }
134
+
135
+ // ----------------- package discovery -----------------
136
+ const SKIP_DIRS = new Set([
137
+ 'node_modules',
138
+ '.git',
139
+ '.next',
140
+ 'dist',
141
+ 'build',
142
+ 'out',
143
+ 'coverage',
144
+ '.turbo',
145
+ ])
146
+
147
+ async function findPackageJsonFilesRecursive(startDir: string): Promise<string[]> {
148
+ const found: string[] = []
149
+
150
+ async function walk(dir: string): Promise<void> {
151
+ // Always resolve dir relative to working directory
152
+ const absDir = path.resolve(process.cwd(), dir)
153
+ const entries = await fs.readdir(absDir, { withFileTypes: true }).catch(() => [])
154
+ for (const e of entries) {
155
+ const full = path.join(absDir, e.name)
156
+ if (e.isDirectory()) {
157
+ if (!SKIP_DIRS.has(e.name)) await walk(full)
158
+ } else if (e.isFile() && e.name === 'package.json') {
159
+ found.push(full)
160
+ }
161
+ }
162
+ }
163
+
164
+ await walk(startDir)
165
+ return found
166
+ }
167
+
168
+ async function collectPackagesFromWorkspaceRoots(workspaceRoots: string[]): Promise<PackageInfo[]> {
169
+ const pkgJsonFiles: string[] = []
170
+
171
+ for (const root of workspaceRoots) {
172
+ if (await exists(root)) {
173
+ pkgJsonFiles.push(...(await findPackageJsonFilesRecursive(root)))
174
+ }
175
+ }
176
+
177
+ const packages: PackageInfo[] = []
178
+ for (const file of [...new Set(pkgJsonFiles)]) {
179
+ if (path.resolve(file) === path.resolve(ROOT_PKG_JSON)) continue
180
+
181
+ try {
182
+ const pj = await readJson<any>(file)
183
+ const dir = path.dirname(file)
184
+ packages.push({
185
+ name: pj.name || toPosix(path.relative(REPO_ROOT, dir)),
186
+ dir,
187
+ scripts: isObject(pj.scripts) ? (pj.scripts as Record<string, string>) : {},
188
+ })
189
+ } catch {}
190
+ }
191
+
192
+ const seen = new Set<string>()
193
+ return packages
194
+ .sort((a, b) => a.name.localeCompare(b.name))
195
+ .filter((p) => (seen.has(p.name) ? false : seen.add(p.name)))
196
+ }
197
+
198
+ // ----------------- classification -----------------
199
+ function classifyPackage(pkg: PackageInfo): string {
200
+ const p = toPosix(pkg.dir)
201
+ if (p.startsWith('apps/')) return 'Apps'
202
+ if (p.startsWith('packages/')) return 'Libraries'
203
+ return 'Other'
204
+ }
205
+ // ----------------- markdown generation -----------------
206
+ function formatQuickStart(pkgMgr: string): string[] {
207
+ return [
208
+ '# 🗂️ Workspace Overview',
209
+ '',
210
+ 'This document explains how to run and discover commands in this monorepo.',
211
+ '',
212
+ '---',
213
+ '',
214
+ '## 🚀 Quick Start',
215
+ '',
216
+ 'Most developers only need the following:',
217
+ '',
218
+ '```bash',
219
+ `${pkgMgr} dev`,
220
+ `${pkgMgr} serve`,
221
+ `${pkgMgr} mobile`,
222
+ `${pkgMgr} help`,
223
+ '```',
224
+ '',
225
+ 'Use `yarn mono` whenever possible. It handles environment setup,',
226
+ 'workspace routing, and service coordination automatically.',
227
+ '',
228
+ '---',
229
+ '',
230
+ ]
231
+ }
232
+
233
+ function formatReferenceIntro(): string[] {
234
+ return [
235
+ '## 📖 Reference',
236
+ '',
237
+ 'This section lists all workspace packages and their available scripts.',
238
+ '',
239
+ 'Use this when:',
240
+ '- Debugging',
241
+ '- Working on internal libraries',
242
+ '- Running CI or low-level tooling',
243
+ '',
244
+ ]
245
+ }
246
+
247
+ function formatIndex(packages: PackageInfo[]): string[] {
248
+ const groups: Record<string, PackageInfo[]> = {}
249
+ for (const p of packages) {
250
+ const g = classifyPackage(p)
251
+ groups[g] ||= []
252
+ groups[g].push(p)
253
+ }
254
+
255
+ const lines: string[] = ['## Workspace Index', '']
256
+ for (const group of Object.keys(groups)) {
257
+ lines.push(`### ${group}`)
258
+ lines.push('')
259
+ for (const p of groups[group]) {
260
+ lines.push(`- [\`${mdEscapeInline(p.name)}\`](#${slugifyForGithubAnchor(p.name)})`)
261
+ }
262
+ lines.push('')
263
+ }
264
+ return lines
265
+ }
266
+
267
+ function formatPackages(packages: PackageInfo[]): string[] {
268
+ const lines: string[] = []
269
+
270
+ for (const p of packages) {
271
+ lines.push(`### ${p.name}`)
272
+ lines.push('')
273
+ lines.push(`_Location: \`${toPosix(path.relative(REPO_ROOT, p.dir))}\`_`)
274
+ lines.push('')
275
+
276
+ const scripts = Object.keys(p.scripts).sort()
277
+ if (!scripts.length) {
278
+ lines.push('_No scripts defined._')
279
+ lines.push('')
280
+ continue
281
+ }
282
+
283
+ lines.push('| Script | Recommended |')
284
+ lines.push('|------|-------------|')
285
+ for (const s of scripts) {
286
+ lines.push(`| \`${s}\` | \`yarn mono ${p.name} ${s}\` |`)
287
+ }
288
+ lines.push('')
289
+ lines.push('<details>')
290
+ lines.push('<summary>Raw yarn workspace commands</summary>')
291
+ lines.push('')
292
+ for (const s of scripts) {
293
+ lines.push(`- \`yarn workspace ${p.name} ${s}\``)
294
+ }
295
+ lines.push('</details>')
296
+ lines.push('')
297
+ }
298
+
299
+ return lines
300
+ }
301
+
302
+ async function ensureParentDir(filePath: string): Promise<void> {
303
+ // Always resolve parent dir relative to working directory
304
+ const dir = path.resolve(process.cwd(), path.dirname(filePath))
305
+ await fs.mkdir(dir, { recursive: true })
306
+ }
307
+
308
+ // ----------------- main -----------------
309
+ async function main(): Promise<void> {
310
+ // Always resolve all paths relative to working directory
311
+ if (!(await exists(ROOT_PKG_JSON))) {
312
+ throw new Error('Root package.json not found')
313
+ }
314
+
315
+ const rootPkg = await readJson<any>(ROOT_PKG_JSON)
316
+ const workspacePatterns = normalizeWorkspacePatterns(rootPkg.workspaces)
317
+
318
+ const pkgMgr = `${(rootPkg.packageManager || 'yarn').split('@')[0]} mono`
319
+
320
+ const workspaceRoots = await findWorkspaceRoots(REPO_ROOT, workspacePatterns)
321
+ const fallbackRoots = ['packages', 'apps'].map((p) => path.join(REPO_ROOT, p))
322
+ const roots = workspaceRoots.length ? workspaceRoots : fallbackRoots
323
+
324
+ const packages = await collectPackagesFromWorkspaceRoots(roots)
325
+
326
+ const lines: string[] = []
327
+ lines.push(...formatQuickStart(pkgMgr))
328
+ lines.push(...formatReferenceIntro())
329
+ lines.push(...formatIndex(packages))
330
+ lines.push('## Packages')
331
+ lines.push('')
332
+ lines.push(...formatPackages(packages))
333
+
334
+ const val = await generateDocsIndex({
335
+ docsDir: path.join(REPO_ROOT, 'docs'),
336
+ excludeFile: 'workspaces.md',
337
+ })
338
+
339
+ val.split('\n').forEach((line) => lines.push(line))
340
+
341
+ await ensureParentDir(OUTPUT_PATH)
342
+ await fs.writeFile(OUTPUT_PATH, lines.join('\n'), 'utf8')
343
+
344
+ console.log(`✅ Generated ${OUTPUT_PATH}`)
345
+ console.log(`📦 Packages found: ${packages.length}`)
346
+ }
347
+
348
+ main().catch((err) => {
349
+ console.error(err)
350
+ process.exitCode = 1
351
+ })
@@ -0,0 +1,187 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+
4
+ /* ──────────────────────────────────────────────────────────
5
+ * Types
6
+ * ────────────────────────────────────────────────────────── */
7
+
8
+ type WorkspaceDetectResult = {
9
+ cwd: string
10
+ workspaceRoot: string | null
11
+ isWorkspaceRoot: boolean
12
+ configDir: string
13
+ configPath: string
14
+ }
15
+
16
+ type DefaultAppConfig = {
17
+ appleAppId?: string
18
+ androidAppId?: string
19
+ appName?: string
20
+ easProjectId?: string
21
+ appScheme?: string
22
+ }
23
+
24
+ type DefaultDeployConfig = {
25
+ baseDomain?: string
26
+ webSubdomain?: string
27
+ apiSubdomain?: string
28
+ defaultKeyPair?: string
29
+ regions: string[]
30
+ ec2User: string
31
+ warehouseRegion: string
32
+ dbInstanceType: string
33
+ appInstanceType: string
34
+ }
35
+
36
+ const requiredSystemDefaults = {
37
+ ec2User: 'ec2-user',
38
+ regions: ['us-east-1'],
39
+ warehouseRegion: 'us-east-1',
40
+ dbInstanceType: 't3.micro',
41
+ appInstanceType: 't3.large',
42
+ }
43
+
44
+ type ConfigTypeMap = {
45
+ app: DefaultAppConfig
46
+ deployment: DefaultDeployConfig
47
+ }
48
+
49
+ /**
50
+ * If TType is a known key, use the mapped type.
51
+ * Otherwise use TCustom (default = unknown).
52
+ */
53
+ type ResolveConfig<TType extends string, TCustom = unknown> = TType extends keyof ConfigTypeMap
54
+ ? ConfigTypeMap[TType]
55
+ : TCustom
56
+
57
+ /* ──────────────────────────────────────────────────────────
58
+ * Environment helpers
59
+ * ────────────────────────────────────────────────────────── */
60
+
61
+ function isLambdaRuntime(): boolean {
62
+ return !!process.env.AWS_LAMBDA_FUNCTION_NAME
63
+ }
64
+
65
+ /* ──────────────────────────────────────────────────────────
66
+ * Workspace detection (CLI / local dev only)
67
+ * ────────────────────────────────────────────────────────── */
68
+
69
+ function detectWorkspaceAndConfigPath(
70
+ startDir: string,
71
+ configFileName: string
72
+ ): WorkspaceDetectResult {
73
+ const cwd = path.resolve(startDir)
74
+
75
+ const isWorkspaceRootDir = (dir: string): boolean => {
76
+ const pkgPath = path.join(dir, 'package.json')
77
+ if (fs.existsSync(pkgPath)) {
78
+ try {
79
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
80
+ if (pkg?.workspaces) return true
81
+ } catch {
82
+ // ignore
83
+ }
84
+ }
85
+
86
+ const markers = ['pnpm-workspace.yaml', 'lerna.json', 'turbo.json', 'nx.json', '.git']
87
+
88
+ return markers.some((m) => fs.existsSync(path.join(dir, m)))
89
+ }
90
+
91
+ let dir = cwd
92
+ while (true) {
93
+ if (isWorkspaceRootDir(dir)) {
94
+ return {
95
+ cwd,
96
+ workspaceRoot: dir,
97
+ isWorkspaceRoot: dir === cwd,
98
+ configDir: dir,
99
+ configPath: path.join(dir, configFileName),
100
+ }
101
+ }
102
+
103
+ const parent = path.dirname(dir)
104
+ if (parent === dir) break
105
+ dir = parent
106
+ }
107
+
108
+ return {
109
+ cwd,
110
+ workspaceRoot: null,
111
+ isWorkspaceRoot: false,
112
+ configDir: cwd,
113
+ configPath: path.join(cwd, configFileName),
114
+ }
115
+ }
116
+
117
+ /* ──────────────────────────────────────────────────────────
118
+ * Bundled config loader (Lambda runtime)
119
+ * ────────────────────────────────────────────────────────── */
120
+
121
+ function loadConfigFromBundle(fileName: string): unknown | null {
122
+ const bundledPath = path.join(__dirname, fileName)
123
+
124
+ if (fs.existsSync(bundledPath)) {
125
+ return JSON.parse(fs.readFileSync(bundledPath, 'utf8'))
126
+ }
127
+
128
+ return null
129
+ }
130
+
131
+ /* ──────────────────────────────────────────────────────────
132
+ * Public API
133
+ * ────────────────────────────────────────────────────────── */
134
+
135
+ export function loadAppConfig<TCustom = unknown, TType extends string = 'app'>(
136
+ configType: TType = 'app' as TType,
137
+ startDir: string = process.cwd()
138
+ ): { config: ResolveConfig<TType, TCustom>; meta: WorkspaceDetectResult } {
139
+ const fileName = `mono.${configType}.json`
140
+
141
+ // 1. Lambda runtime: load bundled config if present
142
+ if (isLambdaRuntime()) {
143
+ const bundled = loadConfigFromBundle(fileName)
144
+
145
+ if (bundled) {
146
+ return {
147
+ config: bundled as ResolveConfig<TType, TCustom>,
148
+ meta: {
149
+ cwd: __dirname,
150
+ workspaceRoot: null,
151
+ isWorkspaceRoot: false,
152
+ configDir: __dirname,
153
+ configPath: path.join(__dirname, fileName),
154
+ },
155
+ }
156
+ }
157
+ }
158
+
159
+ // 2. CLI / local dev: workspace discovery
160
+ const meta = detectWorkspaceAndConfigPath(startDir, fileName)
161
+
162
+ if (!fs.existsSync(meta.configPath)) {
163
+ const where = meta.workspaceRoot ? `workspace root: ${meta.workspaceRoot}` : `cwd: ${meta.cwd}`
164
+
165
+ throw new Error(`Could not find ${fileName} at ${meta.configPath} (detected from ${where}).`)
166
+ }
167
+
168
+ const raw = fs.readFileSync(meta.configPath, 'utf8')
169
+ const config = JSON.parse(raw) as ResolveConfig<TType, TCustom>
170
+
171
+ // Apply required system defaults
172
+ if (typeof config === 'object' && config !== null) {
173
+ for (const key of Object.keys(requiredSystemDefaults)) {
174
+ // @ts-ignore: index signature
175
+ if (config[key] === undefined || config[key] === null) {
176
+ // @ts-ignore: index signature
177
+ config[key] = requiredSystemDefaults[key]
178
+ }
179
+ }
180
+ }
181
+
182
+ return { config, meta }
183
+ }
184
+
185
+ export const loadProjectConfig = loadAppConfig
186
+
187
+ export { loadMergedEnv } from './merge-env'
@@ -0,0 +1,32 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import dotenv from 'dotenv';
4
+
5
+ export function loadMergedEnv(): NodeJS.ProcessEnv {
6
+ const ENV_PATH = path.resolve(process.cwd(), '.env');
7
+ const ENV_LOCAL_PATH = path.resolve(process.cwd(), '.env.local');
8
+
9
+ // Load base .env
10
+ const base =
11
+ fs.existsSync(ENV_PATH) ? dotenv.parse(fs.readFileSync(ENV_PATH)) : {};
12
+
13
+ // Load overrides .env.local
14
+ const local =
15
+ fs.existsSync(ENV_LOCAL_PATH) ?
16
+ dotenv.parse(fs.readFileSync(ENV_LOCAL_PATH))
17
+ : {};
18
+
19
+ // Merge: local overrides base
20
+ const merged = {
21
+ ...base,
22
+ ...local,
23
+ };
24
+
25
+ // Inject into process.env (do NOT overwrite existing real env vars)
26
+ for (const [key, value] of Object.entries(merged)) {
27
+ if (process.env[key] === undefined) {
28
+ process.env[key] = value;
29
+ }
30
+ }
31
+ return process.env;
32
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "rootDir": "./src",
4
+ "outDir": "./dist",
5
+ "target": "ES2022",
6
+ "module": "CommonJS",
7
+ "moduleResolution": "Node",
8
+ "esModuleInterop": true,
9
+ "allowSyntheticDefaultImports": true,
10
+ "declaration": true,
11
+ "declarationMap": true,
12
+ "composite": true,
13
+ "strict": true,
14
+ "skipLibCheck": true,
15
+ "resolveJsonModule": true
16
+ },
17
+ "include": ["src/**/*"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }