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