@ontrails/config 1.0.0-beta.24 → 1.0.0-beta.29
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/CHANGELOG.md +41 -0
- package/README.md +11 -0
- package/package.json +2 -2
- package/src/define-config.ts +6 -18
- package/src/index.ts +24 -0
- package/src/trails-config-file.ts +133 -0
- package/src/trails-conventions.ts +197 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# @ontrails/config
|
|
2
2
|
|
|
3
|
+
## 1.0.0-beta.29
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- @ontrails/core@1.0.0-beta.29
|
|
8
|
+
|
|
9
|
+
## 1.0.0-beta.28
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- @ontrails/core@1.0.0-beta.28
|
|
14
|
+
|
|
15
|
+
## 1.0.0-beta.27
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- @ontrails/core@1.0.0-beta.27
|
|
20
|
+
|
|
21
|
+
## 1.0.0-beta.26
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- 1307568: Centralize Trails config module path conventions, move local config overrides to root `trails.config.local.*`, scaffold the matching gitignore entries, and load project-local Warden rules from `.trails/rules.ts` or `.trails/rules/`.
|
|
26
|
+
- ef09e46: Add shared Trails project-root discovery helpers and use them in Warden so nested
|
|
27
|
+
cwd invocations still load root `trails.config.*` and project-local
|
|
28
|
+
`.trails/rules*` governance.
|
|
29
|
+
- 38cd9d6: Add a shared Trails config file loader that treats `trails.config.ts` as the natural primary while supporting JSON, JSONC, YAML, and TOML peer formats. Release and Warden config loading now consume the same loader and local overrides can be authored as data files.
|
|
30
|
+
- Updated dependencies [1307568]
|
|
31
|
+
- Updated dependencies [371d19e]
|
|
32
|
+
- @ontrails/core@1.0.0-beta.26
|
|
33
|
+
|
|
34
|
+
## 1.0.0-beta.25
|
|
35
|
+
|
|
36
|
+
### Patch Changes
|
|
37
|
+
|
|
38
|
+
- Updated dependencies [c36aca9]
|
|
39
|
+
- Updated dependencies [3befcf1]
|
|
40
|
+
- Updated dependencies [a4f9cf6]
|
|
41
|
+
- Updated dependencies [9bcf34e]
|
|
42
|
+
- @ontrails/core@1.0.0-beta.25
|
|
43
|
+
|
|
3
44
|
## 1.0.0-beta.24
|
|
4
45
|
|
|
5
46
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -90,6 +90,17 @@ Each layer overrides the previous. Environment variables always win.
|
|
|
90
90
|
|
|
91
91
|
`appConfig()` discovers `*.config.toml`, `*.config.json`, `*.config.jsonc`, and `*.config.yaml` by default, plus dotfile equivalents when `dotfile: true`.
|
|
92
92
|
|
|
93
|
+
## Trails project roots
|
|
94
|
+
|
|
95
|
+
`@ontrails/config` owns the shared project-root convention helpers used by framework tools. `resolveTrailsProjectRoot()` honors an explicit root first, then walks upward from a start directory looking for committed project markers:
|
|
96
|
+
|
|
97
|
+
- `trails.config.ts`, `.mts`, `.js`, `.mjs`, `.json`, `.jsonc`,
|
|
98
|
+
`.yaml`, or `.toml`
|
|
99
|
+
- `trails.lock`
|
|
100
|
+
- source-shaped projects with `src/trails/` or `trails/` when no committed marker exists above them
|
|
101
|
+
|
|
102
|
+
`trails.config.local.*` is a per-developer override and does not mark a project root by itself. A bare `.trails/` directory also does not mark a root; it is the committed-control home for project-local sections after a project root is known.
|
|
103
|
+
|
|
93
104
|
## Extensions
|
|
94
105
|
|
|
95
106
|
### `env()`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ontrails/config",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.29",
|
|
4
4
|
"files": [
|
|
5
5
|
"src/**/*.ts",
|
|
6
6
|
"!src/**/__tests__/**",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"clean": "rm -rf dist *.tsbuildinfo"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"@ontrails/core": "^1.0.0-beta.
|
|
25
|
+
"@ontrails/core": "^1.0.0-beta.29",
|
|
26
26
|
"zod": "^4.3.5"
|
|
27
27
|
}
|
|
28
28
|
}
|
package/src/define-config.ts
CHANGED
|
@@ -3,13 +3,11 @@
|
|
|
3
3
|
* framework conventions for profile selection and local overrides.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { existsSync } from 'node:fs';
|
|
7
|
-
import { join } from 'node:path';
|
|
8
|
-
|
|
9
6
|
import type { z } from 'zod';
|
|
10
7
|
|
|
11
8
|
import { appConfig } from './app-config.js';
|
|
12
9
|
import { deriveConfig } from './resolve.js';
|
|
10
|
+
import { loadTrailsLocalConfigValue } from './trails-config-file.js';
|
|
13
11
|
|
|
14
12
|
// ---------------------------------------------------------------------------
|
|
15
13
|
// Types
|
|
@@ -36,13 +34,8 @@ interface DefineConfigResolveOptions {
|
|
|
36
34
|
// Local overrides discovery
|
|
37
35
|
// ---------------------------------------------------------------------------
|
|
38
36
|
|
|
39
|
-
const LOCAL_OVERRIDE_CANDIDATES = [
|
|
40
|
-
'config.local.ts',
|
|
41
|
-
'config.local.js',
|
|
42
|
-
] as const;
|
|
43
|
-
|
|
44
37
|
/**
|
|
45
|
-
* Discover and synchronously import a
|
|
38
|
+
* Discover and synchronously import a `trails.config.local.*` file.
|
|
46
39
|
*
|
|
47
40
|
* Skipped when `TRAILS_ENV=test` for hermetic test environments.
|
|
48
41
|
*/
|
|
@@ -54,15 +47,10 @@ const discoverLocalOverrides = async (
|
|
|
54
47
|
return undefined;
|
|
55
48
|
}
|
|
56
49
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return (mod['default'] ?? mod) as Record<string, unknown>;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return undefined;
|
|
50
|
+
const loaded = await loadTrailsLocalConfigValue(cwd);
|
|
51
|
+
return loaded.value === undefined
|
|
52
|
+
? undefined
|
|
53
|
+
: (loaded.value as Record<string, unknown>);
|
|
66
54
|
};
|
|
67
55
|
|
|
68
56
|
// ---------------------------------------------------------------------------
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,30 @@ export {
|
|
|
9
9
|
export { collectConfigMeta } from './collect.js';
|
|
10
10
|
export { collectResourceConfigs, type ResourceConfigEntry } from './compose.js';
|
|
11
11
|
export { defineConfig, type DefineConfigOptions } from './define-config.js';
|
|
12
|
+
export {
|
|
13
|
+
findTrailsConfigPaths,
|
|
14
|
+
findTrailsLocalConfigPaths,
|
|
15
|
+
trailsConfigDataCandidates,
|
|
16
|
+
trailsConfigFileCandidates,
|
|
17
|
+
findTrailsConfigModulePath,
|
|
18
|
+
findTrailsLocalConfigModulePath,
|
|
19
|
+
findTrailsProjectRoot,
|
|
20
|
+
resolveTrailsProjectRoot,
|
|
21
|
+
trailsConfigModuleCandidates,
|
|
22
|
+
trailsLockFileName,
|
|
23
|
+
trailsLocalConfigDataCandidates,
|
|
24
|
+
trailsLocalConfigFileCandidates,
|
|
25
|
+
trailsLocalConfigModuleCandidates,
|
|
26
|
+
trailsSourceRootCandidates,
|
|
27
|
+
type TrailsProjectRootMarker,
|
|
28
|
+
type TrailsProjectRootResolution,
|
|
29
|
+
} from './trails-conventions.js';
|
|
30
|
+
export {
|
|
31
|
+
loadTrailsConfigFileValue,
|
|
32
|
+
loadTrailsConfigValue,
|
|
33
|
+
loadTrailsLocalConfigValue,
|
|
34
|
+
type LoadedTrailsConfigValue,
|
|
35
|
+
} from './trails-config-file.js';
|
|
12
36
|
export { deriveConfigFields, type FieldDescription } from './derive-fields.js';
|
|
13
37
|
export {
|
|
14
38
|
checkConfig,
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import { NotFoundError, ValidationError } from '@ontrails/core';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
findTrailsConfigPaths,
|
|
9
|
+
findTrailsLocalConfigPaths,
|
|
10
|
+
} from './trails-conventions.js';
|
|
11
|
+
|
|
12
|
+
export interface LoadedTrailsConfigValue {
|
|
13
|
+
readonly configPath?: string | undefined;
|
|
14
|
+
readonly value?: unknown;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const MODULE_EXTENSIONS = new Set(['.ts', '.mts', '.js', '.mjs']);
|
|
18
|
+
const DATA_EXTENSIONS = new Set(['.json', '.jsonc', '.yaml', '.toml']);
|
|
19
|
+
|
|
20
|
+
const extensionFor = (filePath: string): string | undefined => {
|
|
21
|
+
for (const extension of [...MODULE_EXTENSIONS, ...DATA_EXTENSIONS]) {
|
|
22
|
+
if (filePath.endsWith(extension)) {
|
|
23
|
+
return extension;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const isModuleExtension = (extension: string | undefined): boolean =>
|
|
30
|
+
extension !== undefined && MODULE_EXTENSIONS.has(extension);
|
|
31
|
+
|
|
32
|
+
const parseDataConfig = (filePath: string, text: string): unknown => {
|
|
33
|
+
const extension = extensionFor(filePath);
|
|
34
|
+
try {
|
|
35
|
+
switch (extension) {
|
|
36
|
+
case '.json': {
|
|
37
|
+
return JSON.parse(text);
|
|
38
|
+
}
|
|
39
|
+
case '.jsonc': {
|
|
40
|
+
return Bun.JSONC.parse(text);
|
|
41
|
+
}
|
|
42
|
+
case '.toml': {
|
|
43
|
+
return Bun.TOML.parse(text);
|
|
44
|
+
}
|
|
45
|
+
case '.yaml': {
|
|
46
|
+
return Bun.YAML.parse(text);
|
|
47
|
+
}
|
|
48
|
+
default: {
|
|
49
|
+
throw new ValidationError(
|
|
50
|
+
`Unsupported Trails config file: ${filePath}`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
} catch (error) {
|
|
55
|
+
if (error instanceof ValidationError) {
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
throw new ValidationError(
|
|
59
|
+
`Failed to parse Trails config file: ${filePath}`,
|
|
60
|
+
{
|
|
61
|
+
cause: error instanceof Error ? error : new Error(String(error)),
|
|
62
|
+
context: { path: filePath },
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const loadTrailsConfigFileValue = async (
|
|
69
|
+
filePath: string
|
|
70
|
+
): Promise<unknown> => {
|
|
71
|
+
const extension = extensionFor(filePath);
|
|
72
|
+
if (isModuleExtension(extension)) {
|
|
73
|
+
const url = pathToFileURL(filePath);
|
|
74
|
+
url.searchParams.set('t', Date.now().toString());
|
|
75
|
+
const mod = (await import(url.href)) as Record<string, unknown>;
|
|
76
|
+
return mod['default'] ?? mod;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const text = await Bun.file(filePath).text();
|
|
80
|
+
return parseDataConfig(filePath, text);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const findSingleConfigPath = (
|
|
84
|
+
paths: readonly string[],
|
|
85
|
+
label: string
|
|
86
|
+
): string | undefined => {
|
|
87
|
+
if (paths.length <= 1) {
|
|
88
|
+
return paths[0];
|
|
89
|
+
}
|
|
90
|
+
throw new ValidationError(
|
|
91
|
+
`Multiple ${label} config files found: ${paths.join(', ')}. Keep one config file per project root.`
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const loadTrailsConfigValue = async ({
|
|
96
|
+
configPath,
|
|
97
|
+
rootDir,
|
|
98
|
+
}: {
|
|
99
|
+
readonly configPath?: string | undefined;
|
|
100
|
+
readonly rootDir: string;
|
|
101
|
+
}): Promise<LoadedTrailsConfigValue> => {
|
|
102
|
+
const located =
|
|
103
|
+
configPath === undefined
|
|
104
|
+
? findSingleConfigPath(findTrailsConfigPaths(rootDir), 'Trails')
|
|
105
|
+
: resolve(rootDir, configPath);
|
|
106
|
+
|
|
107
|
+
if (located === undefined) {
|
|
108
|
+
return {};
|
|
109
|
+
}
|
|
110
|
+
if (!existsSync(located)) {
|
|
111
|
+
throw new NotFoundError(`Trails config file not found: ${located}`, {
|
|
112
|
+
context: { path: located },
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
configPath: located,
|
|
118
|
+
value: await loadTrailsConfigFileValue(located),
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const loadTrailsLocalConfigValue = async (
|
|
123
|
+
rootDir: string
|
|
124
|
+
): Promise<LoadedTrailsConfigValue> => {
|
|
125
|
+
const located = findSingleConfigPath(
|
|
126
|
+
findTrailsLocalConfigPaths(rootDir),
|
|
127
|
+
'Trails local'
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
return located === undefined
|
|
131
|
+
? {}
|
|
132
|
+
: { configPath: located, value: await loadTrailsConfigFileValue(located) };
|
|
133
|
+
};
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { statSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
export const trailsConfigModuleCandidates = [
|
|
5
|
+
'trails.config.ts',
|
|
6
|
+
'trails.config.mts',
|
|
7
|
+
'trails.config.js',
|
|
8
|
+
'trails.config.mjs',
|
|
9
|
+
] as const;
|
|
10
|
+
|
|
11
|
+
export const trailsConfigDataCandidates = [
|
|
12
|
+
'trails.config.json',
|
|
13
|
+
'trails.config.jsonc',
|
|
14
|
+
'trails.config.yaml',
|
|
15
|
+
'trails.config.toml',
|
|
16
|
+
] as const;
|
|
17
|
+
|
|
18
|
+
export const trailsConfigFileCandidates = [
|
|
19
|
+
...trailsConfigModuleCandidates,
|
|
20
|
+
...trailsConfigDataCandidates,
|
|
21
|
+
] as const;
|
|
22
|
+
|
|
23
|
+
export const trailsLocalConfigModuleCandidates = [
|
|
24
|
+
'trails.config.local.ts',
|
|
25
|
+
'trails.config.local.mts',
|
|
26
|
+
'trails.config.local.js',
|
|
27
|
+
'trails.config.local.mjs',
|
|
28
|
+
] as const;
|
|
29
|
+
|
|
30
|
+
export const trailsLocalConfigDataCandidates = [
|
|
31
|
+
'trails.config.local.json',
|
|
32
|
+
'trails.config.local.jsonc',
|
|
33
|
+
'trails.config.local.yaml',
|
|
34
|
+
'trails.config.local.toml',
|
|
35
|
+
] as const;
|
|
36
|
+
|
|
37
|
+
export const trailsLocalConfigFileCandidates = [
|
|
38
|
+
...trailsLocalConfigModuleCandidates,
|
|
39
|
+
...trailsLocalConfigDataCandidates,
|
|
40
|
+
] as const;
|
|
41
|
+
|
|
42
|
+
export const trailsLockFileName = 'trails.lock' as const;
|
|
43
|
+
|
|
44
|
+
export const trailsSourceRootCandidates = ['src/trails', 'trails'] as const;
|
|
45
|
+
|
|
46
|
+
export type TrailsProjectRootMarker =
|
|
47
|
+
| 'config'
|
|
48
|
+
| 'explicit'
|
|
49
|
+
| 'fallback'
|
|
50
|
+
| 'lock'
|
|
51
|
+
| 'source';
|
|
52
|
+
|
|
53
|
+
export interface TrailsProjectRootResolution {
|
|
54
|
+
readonly marker: TrailsProjectRootMarker;
|
|
55
|
+
readonly markerPath?: string | undefined;
|
|
56
|
+
readonly rootDir: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface FindTrailsProjectRootOptions {
|
|
60
|
+
readonly startDir?: string | undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ResolveTrailsProjectRootOptions extends FindTrailsProjectRootOptions {
|
|
64
|
+
readonly explicitRootDir?: string | undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const isFile = (path: string): boolean => {
|
|
68
|
+
try {
|
|
69
|
+
return statSync(path).isFile();
|
|
70
|
+
} catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const isDirectory = (path: string): boolean => {
|
|
76
|
+
try {
|
|
77
|
+
return statSync(path).isDirectory();
|
|
78
|
+
} catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const firstExistingCandidate = (
|
|
84
|
+
rootDir: string,
|
|
85
|
+
candidates: readonly string[]
|
|
86
|
+
): string | undefined =>
|
|
87
|
+
candidates.map((entry) => resolve(rootDir, entry)).find(isFile);
|
|
88
|
+
|
|
89
|
+
const existingCandidates = (
|
|
90
|
+
rootDir: string,
|
|
91
|
+
candidates: readonly string[]
|
|
92
|
+
): readonly string[] =>
|
|
93
|
+
candidates.map((entry) => resolve(rootDir, entry)).filter(isFile);
|
|
94
|
+
|
|
95
|
+
export const findTrailsConfigPaths = (rootDir: string): readonly string[] =>
|
|
96
|
+
existingCandidates(rootDir, trailsConfigFileCandidates);
|
|
97
|
+
|
|
98
|
+
export const findTrailsLocalConfigPaths = (
|
|
99
|
+
rootDir: string
|
|
100
|
+
): readonly string[] =>
|
|
101
|
+
existingCandidates(rootDir, trailsLocalConfigFileCandidates);
|
|
102
|
+
|
|
103
|
+
export const findTrailsConfigModulePath = ({
|
|
104
|
+
configPath,
|
|
105
|
+
rootDir,
|
|
106
|
+
}: {
|
|
107
|
+
readonly configPath?: string | undefined;
|
|
108
|
+
readonly rootDir: string;
|
|
109
|
+
}): string | undefined => {
|
|
110
|
+
if (configPath !== undefined) {
|
|
111
|
+
return resolve(rootDir, configPath);
|
|
112
|
+
}
|
|
113
|
+
return firstExistingCandidate(rootDir, trailsConfigFileCandidates);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export const findTrailsLocalConfigModulePath = (
|
|
117
|
+
rootDir: string
|
|
118
|
+
): string | undefined =>
|
|
119
|
+
firstExistingCandidate(rootDir, trailsLocalConfigFileCandidates);
|
|
120
|
+
|
|
121
|
+
const firstExistingSourceRoot = (rootDir: string): string | undefined =>
|
|
122
|
+
trailsSourceRootCandidates
|
|
123
|
+
.map((entry) => join(rootDir, entry))
|
|
124
|
+
.find(isDirectory);
|
|
125
|
+
|
|
126
|
+
const findProjectRootMarkerIn = (
|
|
127
|
+
rootDir: string
|
|
128
|
+
): Omit<TrailsProjectRootResolution, 'rootDir'> | undefined => {
|
|
129
|
+
const configPath = findTrailsConfigModulePath({ rootDir });
|
|
130
|
+
if (configPath !== undefined) {
|
|
131
|
+
return { marker: 'config', markerPath: configPath };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const lockPath = join(rootDir, trailsLockFileName);
|
|
135
|
+
if (isFile(lockPath)) {
|
|
136
|
+
return { marker: 'lock', markerPath: lockPath };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return undefined;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const findSourceRootMarkerIn = (
|
|
143
|
+
rootDir: string
|
|
144
|
+
): Omit<TrailsProjectRootResolution, 'rootDir'> | undefined => {
|
|
145
|
+
const sourcePath = firstExistingSourceRoot(rootDir);
|
|
146
|
+
if (sourcePath !== undefined) {
|
|
147
|
+
return { marker: 'source', markerPath: sourcePath };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return undefined;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export const findTrailsProjectRoot = ({
|
|
154
|
+
startDir = process.cwd(),
|
|
155
|
+
}: FindTrailsProjectRootOptions = {}):
|
|
156
|
+
| TrailsProjectRootResolution
|
|
157
|
+
| undefined => {
|
|
158
|
+
let current = resolve(startDir);
|
|
159
|
+
let sourceFallback: TrailsProjectRootResolution | undefined;
|
|
160
|
+
|
|
161
|
+
while (true) {
|
|
162
|
+
const marker = findProjectRootMarkerIn(current);
|
|
163
|
+
if (marker !== undefined) {
|
|
164
|
+
return { ...marker, rootDir: current };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const sourceMarker = findSourceRootMarkerIn(current);
|
|
168
|
+
if (sourceMarker !== undefined) {
|
|
169
|
+
sourceFallback = { ...sourceMarker, rootDir: current };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const parent = dirname(current);
|
|
173
|
+
if (parent === current) {
|
|
174
|
+
return sourceFallback;
|
|
175
|
+
}
|
|
176
|
+
current = parent;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export const resolveTrailsProjectRoot = ({
|
|
181
|
+
explicitRootDir,
|
|
182
|
+
startDir = process.cwd(),
|
|
183
|
+
}: ResolveTrailsProjectRootOptions = {}): TrailsProjectRootResolution => {
|
|
184
|
+
if (explicitRootDir !== undefined) {
|
|
185
|
+
return {
|
|
186
|
+
marker: 'explicit',
|
|
187
|
+
rootDir: resolve(startDir, explicitRootDir),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
findTrailsProjectRoot({ startDir }) ?? {
|
|
193
|
+
marker: 'fallback',
|
|
194
|
+
rootDir: resolve(startDir),
|
|
195
|
+
}
|
|
196
|
+
);
|
|
197
|
+
};
|