@ontrails/trails 1.0.0-beta.30 → 1.0.0-beta.32
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 +89 -0
- package/README.md +2 -2
- package/package.json +16 -16
- package/src/cli.ts +1 -0
- package/src/mcp-options.ts +1 -0
- package/src/regrade/config.ts +152 -0
- package/src/release/pack-coherence.ts +1 -3
- package/src/trails/create-adapter.ts +19 -79
- package/src/trails/deprecate.ts +1 -1
- package/src/trails/guide.ts +4 -4
- package/src/trails/load-app.ts +21 -72
- package/src/trails/regrade.ts +227 -7
- package/src/trails/revise.ts +1 -1
- package/src/trails/survey.ts +12 -12
- package/src/trails/topo-activation.ts +14 -385
- package/src/trails/topo-read-support.ts +13 -13
- package/src/trails/topo.ts +2 -2
- package/src/trails/version-lifecycle-support.ts +11 -11
- package/src/trails/warden-guide.ts +9 -4
- package/src/trails/warden.ts +15 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,94 @@
|
|
|
1
1
|
# trails
|
|
2
2
|
|
|
3
|
+
## 1.0.0-beta.32
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f3c4fef: Export a shared `escapeRegExp` helper from core and migrate first-party callers off local copies.
|
|
8
|
+
- cb0a9d8: Export shared workspace package discovery helpers from core and migrate first-party discovery callers.
|
|
9
|
+
- 8db145e: Move activation report derivation into Topographer and keep the Trails app
|
|
10
|
+
consuming the owner-held activation facts through a compatibility re-export.
|
|
11
|
+
- 8e2603c: Rename read-only topo helper exports from `build*` to `derive*`.
|
|
12
|
+
- 7a3a25f: Rename version lifecycle result paths from `file` to `filePath`.
|
|
13
|
+
- fe72b84: Fold remaining Regrade and Warden scan-target surfaces onto the shared path-scope vocabulary.
|
|
14
|
+
- Updated dependencies [3e5c0fc]
|
|
15
|
+
- Updated dependencies [f3c4fef]
|
|
16
|
+
- Updated dependencies [cb0a9d8]
|
|
17
|
+
- Updated dependencies [8db145e]
|
|
18
|
+
- Updated dependencies [2b819f4]
|
|
19
|
+
- Updated dependencies [21c6dda]
|
|
20
|
+
- Updated dependencies [860ef32]
|
|
21
|
+
- Updated dependencies [fe72b84]
|
|
22
|
+
- @ontrails/adapter-kit@1.0.0-beta.32
|
|
23
|
+
- @ontrails/core@1.0.0-beta.32
|
|
24
|
+
- @ontrails/permits@1.0.0-beta.32
|
|
25
|
+
- @ontrails/warden@1.0.0-beta.32
|
|
26
|
+
- @ontrails/regrade@1.0.0-beta.32
|
|
27
|
+
- @ontrails/topographer@1.0.0-beta.32
|
|
28
|
+
- @ontrails/config@1.0.0-beta.32
|
|
29
|
+
- @ontrails/cli@1.0.0-beta.32
|
|
30
|
+
- @ontrails/commander@1.0.0-beta.32
|
|
31
|
+
- @ontrails/http@1.0.0-beta.32
|
|
32
|
+
- @ontrails/mcp@1.0.0-beta.32
|
|
33
|
+
- @ontrails/observe@1.0.0-beta.32
|
|
34
|
+
- @ontrails/tracing@1.0.0-beta.32
|
|
35
|
+
- @ontrails/wayfinder@1.0.0-beta.32
|
|
36
|
+
|
|
37
|
+
## 1.0.0-beta.31
|
|
38
|
+
|
|
39
|
+
### Patch Changes
|
|
40
|
+
|
|
41
|
+
- e2f3d23: Default Regrade reports to actionable entries, add skip counts grouped by
|
|
42
|
+
reason, and expose an `includeEntries` option for full report inventories.
|
|
43
|
+
- 9be2b7e: Load project-local Warden term-rewrite rules from the Regrade root so repo-owned
|
|
44
|
+
migration classes can run through `trails regrade`.
|
|
45
|
+
- 47f782c: Add occurrence-level vocabulary regrade reports with plan, ledger,
|
|
46
|
+
and completion-gate facts. The Trails `regrade` operator command now supports
|
|
47
|
+
positional `<from> <to>` regrade runs and exposes the same capability through
|
|
48
|
+
the curated MCP surface.
|
|
49
|
+
- ee9f3ae: Let Warden fix capabilities declare downstream scan targets and have Regrade
|
|
50
|
+
honor those targets for Warden-backed term-rewrite classes.
|
|
51
|
+
|
|
52
|
+
Dogfood the first safe facet-to-trailhead prose rewrite through project-local
|
|
53
|
+
Warden rules and Regrade.
|
|
54
|
+
|
|
55
|
+
- 982a4d7: Add Regrade path-scope exclusion globs for vocabulary runs and expose them
|
|
56
|
+
through the `trails regrade` CLI/MCP contract.
|
|
57
|
+
- 1540233: Add Regrade scan inventory summaries that group matched files by extension and
|
|
58
|
+
top-level path, with occurrence counts for vocabulary regrade reports.
|
|
59
|
+
- de878bd: Let Trails project config provide default Regrade vocabulary scope settings,
|
|
60
|
+
with explicit CLI or MCP plan inputs overriding those defaults.
|
|
61
|
+
- a0126d9: Add Warden `scope.exclude` globs through project config and the Trails CLI
|
|
62
|
+
wrapper so governance runs can exclude local notes, scratch space, and generated
|
|
63
|
+
state without dropping durable skills or plugin assets from scope.
|
|
64
|
+
- a079073: Rename Regrade path-scope scan controls from `ignore` to `exclude` across CLI, MCP, and project config.
|
|
65
|
+
- 6a26a08: Rename Warden governance scope controls from jurisdiction ignore settings to `scope.exclude` across config, CLI, and Trails surfaces.
|
|
66
|
+
- Updated dependencies [e2f3d23]
|
|
67
|
+
- Updated dependencies [9be2b7e]
|
|
68
|
+
- Updated dependencies [47f782c]
|
|
69
|
+
- Updated dependencies [ee9f3ae]
|
|
70
|
+
- Updated dependencies [982a4d7]
|
|
71
|
+
- Updated dependencies [1540233]
|
|
72
|
+
- Updated dependencies [a0126d9]
|
|
73
|
+
- Updated dependencies [4cd5d4e]
|
|
74
|
+
- Updated dependencies [a079073]
|
|
75
|
+
- Updated dependencies [6a26a08]
|
|
76
|
+
- Updated dependencies [38907cc]
|
|
77
|
+
- @ontrails/regrade@1.0.0-beta.31
|
|
78
|
+
- @ontrails/warden@1.0.0-beta.31
|
|
79
|
+
- @ontrails/mcp@1.0.0-beta.31
|
|
80
|
+
- @ontrails/wayfinder@1.0.0-beta.31
|
|
81
|
+
- @ontrails/core@1.0.0-beta.31
|
|
82
|
+
- @ontrails/commander@1.0.0-beta.31
|
|
83
|
+
- @ontrails/adapter-kit@1.0.0-beta.31
|
|
84
|
+
- @ontrails/cli@1.0.0-beta.31
|
|
85
|
+
- @ontrails/config@1.0.0-beta.31
|
|
86
|
+
- @ontrails/http@1.0.0-beta.31
|
|
87
|
+
- @ontrails/observe@1.0.0-beta.31
|
|
88
|
+
- @ontrails/permits@1.0.0-beta.31
|
|
89
|
+
- @ontrails/topographer@1.0.0-beta.31
|
|
90
|
+
- @ontrails/tracing@1.0.0-beta.31
|
|
91
|
+
|
|
3
92
|
## 1.0.0-beta.30
|
|
4
93
|
|
|
5
94
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -17,8 +17,8 @@ Common workflows:
|
|
|
17
17
|
- `trails validate` checks root `trails.lock` for drift.
|
|
18
18
|
- `trails wayfind`, `trails wayfind --trails --intent read`, `trails wayfind <id> --contract`, `trails wayfind <id> --deps`, `trails wayfind <id> --impact`, `trails wayfind pattern "wayfind.*"`, `trails wayfind query "release drift"`, `trails wayfind file <file> --outline`, and `trails wayfind diff ...` read graph artifacts and source outlines through Wayfinder for local navigation.
|
|
19
19
|
- `trails schema <command...>` shows accepted CLI routes, aliases, flags, and schemas for an operator command.
|
|
20
|
-
- `trails warden` runs Trails governance checks for contract and architecture drift.
|
|
21
|
-
- `trails regrade --root-dir <path> --json` dry-runs downstream migration checks; add `--apply` only to write safe rewrites.
|
|
20
|
+
- `trails warden` runs Trails governance checks for contract and architecture drift. Use `--scope-exclude <glob>` or project `warden.scope.exclude` config when local notes, scratch space, or generated state should not be governed by Warden.
|
|
21
|
+
- `trails regrade --root-dir <path> --json` dry-runs downstream migration checks; add `--apply` only to write safe rewrites. Use `trails regrade <from> <to> --root-dir <path> --json` for occurrence-level vocabulary regrade reports that include the authored plan, observed ledger, completion gate, and scan inventory by file type and top-level path. Add `--exclude <glob>` to keep migration scope away from local notes, scratch space, generated state, or other paths that should not be scanned for that transition; project config can provide `regrade.scope` defaults and explicit plan inputs override them. The same `regrade` trail is exposed through MCP for agent-driven migration checks.
|
|
22
22
|
- `trails guide` remains available for compatibility; prefer `trails wayfind --source live --module <app-module>` or saved-artifact Wayfinder reads for agent navigation.
|
|
23
23
|
|
|
24
24
|
Trails is contract-first: define trails once with typed input, Result output, examples, and meta; the framework derives CLI, MCP, HTTP, and future surfaces from the same contracts.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ontrails/trails",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.32",
|
|
4
4
|
"bin": {
|
|
5
5
|
"trails": "./bin/trails.ts"
|
|
6
6
|
},
|
|
@@ -27,25 +27,25 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@clack/prompts": "^1.1.0",
|
|
30
|
-
"@ontrails/adapter-kit": "^1.0.0-beta.
|
|
31
|
-
"@ontrails/cli": "^1.0.0-beta.
|
|
32
|
-
"@ontrails/commander": "^1.0.0-beta.
|
|
33
|
-
"@ontrails/config": "^1.0.0-beta.
|
|
34
|
-
"@ontrails/core": "^1.0.0-beta.
|
|
35
|
-
"@ontrails/http": "^1.0.0-beta.
|
|
36
|
-
"@ontrails/mcp": "^1.0.0-beta.
|
|
37
|
-
"@ontrails/observe": "^1.0.0-beta.
|
|
38
|
-
"@ontrails/permits": "^1.0.0-beta.
|
|
39
|
-
"@ontrails/regrade": "^1.0.0-beta.
|
|
40
|
-
"@ontrails/topographer": "^1.0.0-beta.
|
|
41
|
-
"@ontrails/tracing": "^1.0.0-beta.
|
|
42
|
-
"@ontrails/warden": "^1.0.0-beta.
|
|
43
|
-
"@ontrails/wayfinder": "^1.0.0-beta.
|
|
30
|
+
"@ontrails/adapter-kit": "^1.0.0-beta.32",
|
|
31
|
+
"@ontrails/cli": "^1.0.0-beta.32",
|
|
32
|
+
"@ontrails/commander": "^1.0.0-beta.32",
|
|
33
|
+
"@ontrails/config": "^1.0.0-beta.32",
|
|
34
|
+
"@ontrails/core": "^1.0.0-beta.32",
|
|
35
|
+
"@ontrails/http": "^1.0.0-beta.32",
|
|
36
|
+
"@ontrails/mcp": "^1.0.0-beta.32",
|
|
37
|
+
"@ontrails/observe": "^1.0.0-beta.32",
|
|
38
|
+
"@ontrails/permits": "^1.0.0-beta.32",
|
|
39
|
+
"@ontrails/regrade": "^1.0.0-beta.32",
|
|
40
|
+
"@ontrails/topographer": "^1.0.0-beta.32",
|
|
41
|
+
"@ontrails/tracing": "^1.0.0-beta.32",
|
|
42
|
+
"@ontrails/warden": "^1.0.0-beta.32",
|
|
43
|
+
"@ontrails/wayfinder": "^1.0.0-beta.32",
|
|
44
44
|
"commander": "^14.0.3",
|
|
45
45
|
"typescript": "^5.9.3",
|
|
46
46
|
"zod": "^4.3.5"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@ontrails/testing": "^1.0.0-beta.
|
|
49
|
+
"@ontrails/testing": "^1.0.0-beta.32"
|
|
50
50
|
}
|
|
51
51
|
}
|
package/src/cli.ts
CHANGED
package/src/mcp-options.ts
CHANGED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { loadTrailsConfigValue } from '@ontrails/config';
|
|
2
|
+
import {
|
|
3
|
+
InternalError,
|
|
4
|
+
Result,
|
|
5
|
+
ValidationError,
|
|
6
|
+
pathScopeSchema,
|
|
7
|
+
} from '@ontrails/core';
|
|
8
|
+
import type { Result as TrailsResult } from '@ontrails/core';
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
|
|
11
|
+
export const regradeConfigSchema = z
|
|
12
|
+
.object({
|
|
13
|
+
scope: pathScopeSchema.optional(),
|
|
14
|
+
})
|
|
15
|
+
.default({});
|
|
16
|
+
|
|
17
|
+
export type RegradeConfig = z.output<typeof regradeConfigSchema>;
|
|
18
|
+
|
|
19
|
+
interface RegradeConfigLoadResult {
|
|
20
|
+
readonly config?: RegradeConfig;
|
|
21
|
+
readonly configPath?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface ResolvableConfig {
|
|
25
|
+
readonly resolve: (options?: {
|
|
26
|
+
readonly cwd?: string;
|
|
27
|
+
readonly env?: Record<string, string | undefined>;
|
|
28
|
+
}) => unknown;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
32
|
+
typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
33
|
+
|
|
34
|
+
const isResultLike = (
|
|
35
|
+
value: unknown
|
|
36
|
+
): value is {
|
|
37
|
+
readonly error?: unknown;
|
|
38
|
+
readonly isErr: () => boolean;
|
|
39
|
+
readonly isOk: () => boolean;
|
|
40
|
+
readonly value?: unknown;
|
|
41
|
+
} =>
|
|
42
|
+
isRecord(value) &&
|
|
43
|
+
typeof value['isOk'] === 'function' &&
|
|
44
|
+
typeof value['isErr'] === 'function';
|
|
45
|
+
|
|
46
|
+
const isResolvableConfig = (value: unknown): value is ResolvableConfig =>
|
|
47
|
+
isRecord(value) && typeof value['resolve'] === 'function';
|
|
48
|
+
|
|
49
|
+
const errorMessage = (error: unknown): string =>
|
|
50
|
+
error instanceof Error ? error.message : String(error);
|
|
51
|
+
|
|
52
|
+
const extractRegradeConfig = (
|
|
53
|
+
value: unknown
|
|
54
|
+
): TrailsResult<RegradeConfig | undefined, ValidationError> => {
|
|
55
|
+
if (!(isRecord(value) && 'regrade' in value)) {
|
|
56
|
+
return Result.ok();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const parsed = regradeConfigSchema.safeParse(value['regrade']);
|
|
60
|
+
if (parsed.success) {
|
|
61
|
+
return Result.ok(parsed.data);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return Result.err(
|
|
65
|
+
new ValidationError('Invalid regrade config in Trails config file.', {
|
|
66
|
+
context: { issues: parsed.error.issues },
|
|
67
|
+
})
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const loadRegradeConfig = async ({
|
|
72
|
+
configPath,
|
|
73
|
+
env = {},
|
|
74
|
+
rootDir,
|
|
75
|
+
}: {
|
|
76
|
+
readonly configPath?: string | undefined;
|
|
77
|
+
readonly env?: Record<string, string | undefined> | undefined;
|
|
78
|
+
readonly rootDir: string;
|
|
79
|
+
}): Promise<
|
|
80
|
+
TrailsResult<RegradeConfigLoadResult, InternalError | ValidationError>
|
|
81
|
+
> => {
|
|
82
|
+
try {
|
|
83
|
+
const loaded = await loadTrailsConfigValue({
|
|
84
|
+
configPath,
|
|
85
|
+
rootDir,
|
|
86
|
+
});
|
|
87
|
+
const exported = loaded.value;
|
|
88
|
+
if (exported === undefined) {
|
|
89
|
+
return Result.ok({});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (isResolvableConfig(exported)) {
|
|
93
|
+
const resolved = await exported.resolve({ cwd: rootDir, env });
|
|
94
|
+
if (isResultLike(resolved)) {
|
|
95
|
+
if (resolved.isErr()) {
|
|
96
|
+
return Result.err(
|
|
97
|
+
new InternalError(
|
|
98
|
+
`Failed to resolve regrade config: ${errorMessage(resolved.error)}`
|
|
99
|
+
)
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
const configResult = extractRegradeConfig(resolved.value);
|
|
103
|
+
if (configResult.isErr()) {
|
|
104
|
+
return configResult;
|
|
105
|
+
}
|
|
106
|
+
return Result.ok({
|
|
107
|
+
...(configResult.value === undefined
|
|
108
|
+
? {}
|
|
109
|
+
: { config: configResult.value }),
|
|
110
|
+
...(loaded.configPath === undefined
|
|
111
|
+
? {}
|
|
112
|
+
: { configPath: loaded.configPath }),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const configResult = extractRegradeConfig(resolved);
|
|
117
|
+
if (configResult.isErr()) {
|
|
118
|
+
return configResult;
|
|
119
|
+
}
|
|
120
|
+
return Result.ok({
|
|
121
|
+
...(configResult.value === undefined
|
|
122
|
+
? {}
|
|
123
|
+
: { config: configResult.value }),
|
|
124
|
+
...(loaded.configPath === undefined
|
|
125
|
+
? {}
|
|
126
|
+
: { configPath: loaded.configPath }),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const configResult = extractRegradeConfig(exported);
|
|
131
|
+
if (configResult.isErr()) {
|
|
132
|
+
return configResult;
|
|
133
|
+
}
|
|
134
|
+
return Result.ok({
|
|
135
|
+
...(configResult.value === undefined
|
|
136
|
+
? {}
|
|
137
|
+
: { config: configResult.value }),
|
|
138
|
+
...(loaded.configPath === undefined
|
|
139
|
+
? {}
|
|
140
|
+
: { configPath: loaded.configPath }),
|
|
141
|
+
});
|
|
142
|
+
} catch (error) {
|
|
143
|
+
return Result.err(
|
|
144
|
+
new InternalError(
|
|
145
|
+
`Failed to load regrade config: ${errorMessage(error)}`,
|
|
146
|
+
{
|
|
147
|
+
cause: error instanceof Error ? error : new Error(String(error)),
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile, readdir, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { join, relative } from 'node:path';
|
|
3
|
+
import { escapeRegExp } from '@ontrails/core';
|
|
3
4
|
|
|
4
5
|
export interface ReleasePackCoherenceInput {
|
|
5
6
|
readonly branchName?: string | undefined;
|
|
@@ -234,9 +235,6 @@ export const findLockfileWorkspaceMetadataMismatches = ({
|
|
|
234
235
|
return mismatches;
|
|
235
236
|
};
|
|
236
237
|
|
|
237
|
-
const escapeRegExp = (text: string): string =>
|
|
238
|
-
text.replaceAll(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
239
|
-
|
|
240
238
|
const findWorkspaceBlockRange = (
|
|
241
239
|
text: string,
|
|
242
240
|
workspacePath: string
|
|
@@ -13,9 +13,16 @@ import type {
|
|
|
13
13
|
AdapterTargetConformanceManifest,
|
|
14
14
|
AdapterTargetPlacement,
|
|
15
15
|
} from '@ontrails/adapter-kit';
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
import {
|
|
17
|
+
findWorkspacePackage,
|
|
18
|
+
listWorkspacePatterns,
|
|
19
|
+
Result,
|
|
20
|
+
trail,
|
|
21
|
+
ValidationError,
|
|
22
|
+
} from '@ontrails/core';
|
|
23
|
+
import type { WorkspaceRootManifest } from '@ontrails/core';
|
|
24
|
+
import { existsSync, readFileSync, realpathSync } from 'node:fs';
|
|
25
|
+
import { dirname, join, resolve } from 'node:path';
|
|
19
26
|
import { z } from 'zod';
|
|
20
27
|
|
|
21
28
|
import {
|
|
@@ -69,20 +76,11 @@ interface AdapterOperationPlan {
|
|
|
69
76
|
readonly targetKey: string;
|
|
70
77
|
}
|
|
71
78
|
|
|
72
|
-
interface RootManifest {
|
|
73
|
-
readonly workspaces?: unknown;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
79
|
interface WorkspacePackageManifest {
|
|
77
80
|
readonly exports?: unknown;
|
|
78
81
|
readonly name?: unknown;
|
|
79
82
|
}
|
|
80
83
|
|
|
81
|
-
interface WorkspacePackageName {
|
|
82
|
-
readonly name: string;
|
|
83
|
-
readonly workspacePath: string;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
84
|
interface LocalNamedReexport {
|
|
87
85
|
readonly local: string;
|
|
88
86
|
readonly specifier: string;
|
|
@@ -123,27 +121,6 @@ const readJson = <T>(path: string): T | undefined => {
|
|
|
123
121
|
}
|
|
124
122
|
};
|
|
125
123
|
|
|
126
|
-
const workspacePatternsFromManifest = (
|
|
127
|
-
manifest: RootManifest | undefined
|
|
128
|
-
): readonly string[] => {
|
|
129
|
-
const { workspaces } = manifest ?? {};
|
|
130
|
-
if (Array.isArray(workspaces)) {
|
|
131
|
-
return workspaces.filter(
|
|
132
|
-
(pattern): pattern is string => typeof pattern === 'string'
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const packages =
|
|
137
|
-
workspaces && typeof workspaces === 'object' && !Array.isArray(workspaces)
|
|
138
|
-
? (workspaces as Record<string, unknown>)['packages']
|
|
139
|
-
: undefined;
|
|
140
|
-
return Array.isArray(packages)
|
|
141
|
-
? packages.filter(
|
|
142
|
-
(pattern): pattern is string => typeof pattern === 'string'
|
|
143
|
-
)
|
|
144
|
-
: [];
|
|
145
|
-
};
|
|
146
|
-
|
|
147
124
|
const normalizeWorkspacePattern = (pattern: string): string =>
|
|
148
125
|
pattern.replace(/^\.\//u, '').replace(/\/+$/u, '');
|
|
149
126
|
|
|
@@ -169,54 +146,14 @@ const rootWorkspaceIncludesPath = (
|
|
|
169
146
|
rootDir: string,
|
|
170
147
|
workspacePath: string
|
|
171
148
|
): boolean => {
|
|
172
|
-
const rootManifest = readJson<
|
|
173
|
-
|
|
149
|
+
const rootManifest = readJson<WorkspaceRootManifest>(
|
|
150
|
+
join(rootDir, 'package.json')
|
|
151
|
+
);
|
|
152
|
+
return listWorkspacePatterns(rootManifest).some((pattern) =>
|
|
174
153
|
workspacePatternCoversPath(pattern, workspacePath)
|
|
175
154
|
);
|
|
176
155
|
};
|
|
177
156
|
|
|
178
|
-
const workspaceDirsForPattern = (
|
|
179
|
-
rootDir: string,
|
|
180
|
-
pattern: string
|
|
181
|
-
): readonly string[] => {
|
|
182
|
-
if (!pattern.endsWith('/*')) {
|
|
183
|
-
const workspaceDir = join(rootDir, pattern);
|
|
184
|
-
return existsSync(workspaceDir) ? [workspaceDir] : [];
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const groupDir = join(rootDir, pattern.slice(0, -2));
|
|
188
|
-
if (!existsSync(groupDir)) {
|
|
189
|
-
return [];
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return readdirSync(groupDir, { withFileTypes: true })
|
|
193
|
-
.filter((entry) => entry.isDirectory())
|
|
194
|
-
.map((entry) => join(groupDir, entry.name))
|
|
195
|
-
.toSorted();
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
const findWorkspacePackageName = (
|
|
199
|
-
rootDir: string,
|
|
200
|
-
packageName: string
|
|
201
|
-
): WorkspacePackageName | undefined => {
|
|
202
|
-
const rootManifest = readJson<RootManifest>(join(rootDir, 'package.json'));
|
|
203
|
-
for (const pattern of workspacePatternsFromManifest(rootManifest)) {
|
|
204
|
-
for (const workspaceDir of workspaceDirsForPattern(rootDir, pattern)) {
|
|
205
|
-
const manifest = readJson<WorkspacePackageManifest>(
|
|
206
|
-
join(workspaceDir, 'package.json')
|
|
207
|
-
);
|
|
208
|
-
if (manifest?.name === packageName) {
|
|
209
|
-
return {
|
|
210
|
-
name: packageName,
|
|
211
|
-
workspacePath: relative(rootDir, workspaceDir),
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return undefined;
|
|
218
|
-
};
|
|
219
|
-
|
|
220
157
|
const resolvePhysicalRootDir = (
|
|
221
158
|
rootDir: string
|
|
222
159
|
): Result<string, ValidationError> => {
|
|
@@ -882,10 +819,13 @@ const buildExtractedPlan = (
|
|
|
882
819
|
if (!packageNamePattern.test(packageName)) {
|
|
883
820
|
return fail(packageNameMessage);
|
|
884
821
|
}
|
|
885
|
-
const existingPackage =
|
|
822
|
+
const existingPackage = findWorkspacePackage<WorkspacePackageManifest>(
|
|
823
|
+
rootDir,
|
|
824
|
+
packageName
|
|
825
|
+
);
|
|
886
826
|
if (existingPackage) {
|
|
887
827
|
return fail(
|
|
888
|
-
`Workspace package name "${
|
|
828
|
+
`Workspace package name "${packageName}" already exists at ${existingPackage.workspacePath}.`
|
|
889
829
|
);
|
|
890
830
|
}
|
|
891
831
|
|
package/src/trails/deprecate.ts
CHANGED
package/src/trails/guide.ts
CHANGED
|
@@ -10,8 +10,8 @@ import { z } from 'zod';
|
|
|
10
10
|
import { withFreshOperatorApp } from './operator-context.js';
|
|
11
11
|
import { trailDetailOutput } from './topo-output-schemas.js';
|
|
12
12
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
deriveCurrentGuideEntries,
|
|
14
|
+
deriveCurrentTopoDetail,
|
|
15
15
|
readSurfaceLayerNamesFromContext,
|
|
16
16
|
} from './topo-read-support.js';
|
|
17
17
|
import { createIsolatedExampleInput } from './topo-support.js';
|
|
@@ -53,7 +53,7 @@ export const guideTrail = trail('guide', {
|
|
|
53
53
|
blaze: async (input: GuideTrailInput, ctx) =>
|
|
54
54
|
withFreshOperatorApp<GuideTrailOutput>(input, ctx, ({ lease, rootDir }) => {
|
|
55
55
|
if (input.trailId) {
|
|
56
|
-
const detail =
|
|
56
|
+
const detail = deriveCurrentTopoDetail(lease.app, input.trailId, {
|
|
57
57
|
cliAliases: lease.cliAliases,
|
|
58
58
|
rootDir,
|
|
59
59
|
surfaceLayerNames: readSurfaceLayerNamesFromContext(ctx),
|
|
@@ -70,7 +70,7 @@ export const guideTrail = trail('guide', {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
return Result.ok({
|
|
73
|
-
entries:
|
|
73
|
+
entries: deriveCurrentGuideEntries(lease.app, {
|
|
74
74
|
rootDir,
|
|
75
75
|
}) as GuideEntry[],
|
|
76
76
|
mode: 'list' as const,
|
package/src/trails/load-app.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
19
19
|
import {
|
|
20
20
|
deriveSafePath,
|
|
21
21
|
isTrailsError,
|
|
22
|
+
listWorkspacePackages,
|
|
22
23
|
PermissionError,
|
|
23
24
|
Result,
|
|
24
25
|
ValidationError,
|
|
@@ -278,6 +279,8 @@ interface PackageJson {
|
|
|
278
279
|
readonly workspaces?: unknown;
|
|
279
280
|
}
|
|
280
281
|
|
|
282
|
+
type NamedPackageJson = PackageJson & { readonly name: string };
|
|
283
|
+
|
|
281
284
|
const readPackageJson = (packagePath: string): PackageJson | undefined => {
|
|
282
285
|
try {
|
|
283
286
|
return JSON.parse(readFileSync(packagePath, 'utf8')) as PackageJson;
|
|
@@ -327,20 +330,6 @@ const isPackageLocalImport = (
|
|
|
327
330
|
const isScannableModule = (modulePath: string): boolean =>
|
|
328
331
|
SCANNABLE_EXTENSIONS.has(extname(modulePath));
|
|
329
332
|
|
|
330
|
-
const resolveImportedModulePath = (
|
|
331
|
-
importerPath: string,
|
|
332
|
-
importPath: string
|
|
333
|
-
): string => {
|
|
334
|
-
const resolved = import.meta.resolve(
|
|
335
|
-
importPath,
|
|
336
|
-
pathToFileURL(importerPath).href
|
|
337
|
-
);
|
|
338
|
-
return resolveFilesystemModulePath(
|
|
339
|
-
fileURLToPath(resolved),
|
|
340
|
-
dirname(importerPath)
|
|
341
|
-
);
|
|
342
|
-
};
|
|
343
|
-
|
|
344
333
|
const readDirectoryEntries = (directoryPath: string): readonly string[] => {
|
|
345
334
|
try {
|
|
346
335
|
return readdirSync(directoryPath);
|
|
@@ -359,25 +348,18 @@ const safeStat = (
|
|
|
359
348
|
}
|
|
360
349
|
};
|
|
361
350
|
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
)
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
Array.isArray(workspaces.packages)
|
|
375
|
-
) {
|
|
376
|
-
return workspaces.packages.filter(
|
|
377
|
-
(pattern): pattern is string => typeof pattern === 'string'
|
|
378
|
-
);
|
|
379
|
-
}
|
|
380
|
-
return [];
|
|
351
|
+
const resolveImportedModulePath = (
|
|
352
|
+
importerPath: string,
|
|
353
|
+
importPath: string
|
|
354
|
+
): string => {
|
|
355
|
+
const resolved = import.meta.resolve(
|
|
356
|
+
importPath,
|
|
357
|
+
pathToFileURL(importerPath).href
|
|
358
|
+
);
|
|
359
|
+
return resolveFilesystemModulePath(
|
|
360
|
+
fileURLToPath(resolved),
|
|
361
|
+
dirname(importerPath)
|
|
362
|
+
);
|
|
381
363
|
};
|
|
382
364
|
|
|
383
365
|
interface WorkspacePackage {
|
|
@@ -386,45 +368,12 @@ interface WorkspacePackage {
|
|
|
386
368
|
readonly packageRoot: string;
|
|
387
369
|
}
|
|
388
370
|
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
const entryPath = join(baseDir, entry);
|
|
396
|
-
if (safeStat(entryPath)?.isDirectory()) {
|
|
397
|
-
roots.push(entryPath);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
continue;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
if (!pattern.includes('*')) {
|
|
404
|
-
roots.push(resolve(cwd, pattern));
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
return roots;
|
|
408
|
-
};
|
|
409
|
-
|
|
410
|
-
const readWorkspacePackages = (cwd: string): readonly WorkspacePackage[] => {
|
|
411
|
-
const packages: WorkspacePackage[] = [];
|
|
412
|
-
for (const packageRoot of listWorkspacePackageRoots(cwd)) {
|
|
413
|
-
const packageJson = readPackageJson(join(packageRoot, 'package.json'));
|
|
414
|
-
if (
|
|
415
|
-
packageJson !== undefined &&
|
|
416
|
-
typeof packageJson.name === 'string' &&
|
|
417
|
-
packageJson.name.length > 0
|
|
418
|
-
) {
|
|
419
|
-
packages.push({
|
|
420
|
-
name: packageJson.name,
|
|
421
|
-
packageJson,
|
|
422
|
-
packageRoot,
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
return packages;
|
|
427
|
-
};
|
|
371
|
+
const readWorkspacePackages = (cwd: string): readonly WorkspacePackage[] =>
|
|
372
|
+
listWorkspacePackages<NamedPackageJson>(cwd).map((workspacePackage) => ({
|
|
373
|
+
name: workspacePackage.manifest.name,
|
|
374
|
+
packageJson: workspacePackage.manifest,
|
|
375
|
+
packageRoot: workspacePackage.packageRoot,
|
|
376
|
+
}));
|
|
428
377
|
|
|
429
378
|
const parsePackageSpecifier = (
|
|
430
379
|
importPath: string
|