@vltpkg/workspaces 0.0.0-0.1730239248325
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/LICENSE +15 -0
- package/README.md +110 -0
- package/dist/esm/index.d.ts +218 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +527 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/package.json +70 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Copyright (c) vlt technology, Inc.
|
|
2
|
+
|
|
3
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
4
|
+
|
|
5
|
+
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
6
|
+
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
7
|
+
Subject to the terms and conditions of this license, each copyright holder and contributor hereby grants to those receiving rights under this license a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except for failure to satisfy the conditions of this license) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer this software, where such license applies only to those patent claims, already acquired or hereafter acquired, licensable by such copyright holder or contributor that are necessarily infringed by:
|
|
8
|
+
|
|
9
|
+
(a) their Contribution(s) (the licensed copyrights of copyright holders and non-copyrightable additions of contributors, in source or binary form) alone; or
|
|
10
|
+
(b) combination of their Contribution(s) with the work of authorship to which such Contribution(s) was added by such copyright holder or contributor, if, at the time the Contribution is added, such addition causes such combination to be necessarily infringed. The patent license shall not apply to any other combinations which include the Contribution.
|
|
11
|
+
Except as expressly stated above, no rights or licenses from any copyright holder or contributor is granted under this license, whether expressly, by implication, estoppel or otherwise.
|
|
12
|
+
|
|
13
|
+
DISCLAIMER
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# `@vltpkg/workspaces`
|
|
2
|
+
|
|
3
|
+
Utility for working with vlt workspaces.
|
|
4
|
+
|
|
5
|
+
## USAGE
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
import { Monorepo, Workspace } from '@vltpkg/workspaces'
|
|
9
|
+
|
|
10
|
+
const m = await Monorepo.load()
|
|
11
|
+
// Can also provide a path option, defaults to process.cwd()
|
|
12
|
+
// m = await Monorepo.load(someOtherPath)
|
|
13
|
+
// Constructor can be used directly, but less convenient:
|
|
14
|
+
// const m = new Monorepo()
|
|
15
|
+
// await m.load()
|
|
16
|
+
|
|
17
|
+
const appWS = m.get('@acme/app')
|
|
18
|
+
console.log(appWS.path) // packages/apps/app
|
|
19
|
+
console.log(appWS.groups) // ['apps', 'frontend']
|
|
20
|
+
console.log(await appWS.manifest()) // { name: "@acme/app", version: etc }
|
|
21
|
+
console.log(appWS.inGroup('frontend')) // true
|
|
22
|
+
console.log(appWS.inGroup('datalayer')) // false
|
|
23
|
+
assert.equal(m.get('packages/apps/app'), appWS) // can get by path
|
|
24
|
+
|
|
25
|
+
for (const workspace of m) {
|
|
26
|
+
// iterate over the workspaces in topological dependency order
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// run a script or some other async operation, in topological
|
|
30
|
+
// dependency graph order. Eg, for a project containing
|
|
31
|
+
// workspaces a, b, c, d, where a depends on b and c, and b
|
|
32
|
+
// and c both depend on d, it will run first for d, then for b
|
|
33
|
+
// and c in parallel, then for a when c and b are both resolved.
|
|
34
|
+
// depResults is a map of [ws, result] of all dependency
|
|
35
|
+
// workspaces that were completed prior to this one
|
|
36
|
+
// AbortSignal can be used to stop the operation.
|
|
37
|
+
m.run(async (ws, signal, depResults) => {
|
|
38
|
+
await doSomethingAsync(ws, signal)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
// methods that return a set of Workspace objects actually return
|
|
42
|
+
// Monorepo object with all the same methods.
|
|
43
|
+
console.log(m.group('app')) // Monorepo object containing only those keys
|
|
44
|
+
m.group('app').run(async ([name, ws]) => {
|
|
45
|
+
// logs all the workspaces in the 'app' group
|
|
46
|
+
console.log(name, ws)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
// Since loading everything can be slow, and some actions don't
|
|
50
|
+
// require loading the entire set, you can use this method if you
|
|
51
|
+
// want to just want to load a single workspace or those matching
|
|
52
|
+
// a specific pattern.
|
|
53
|
+
const apps = new Monorepo()
|
|
54
|
+
apps.load({ groups: 'apps' })
|
|
55
|
+
const { manifest } = app.get('packages/my-app')
|
|
56
|
+
|
|
57
|
+
// get workspace names in no particular order
|
|
58
|
+
for (const name of m.names()) {
|
|
59
|
+
// @vltpkg/cache, @vltpkg/semver, etc.
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// get workspace paths in no particular order
|
|
63
|
+
for (const path of m.paths()) {
|
|
64
|
+
// apps/webapp, utils/is-even, etc.
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Configuration is stored in the project root at
|
|
69
|
+
`vlt-workspaces.json`. The type of the object in the file must
|
|
70
|
+
be:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
type WorkspaceConfiguration =
|
|
74
|
+
| string
|
|
75
|
+
| string[]
|
|
76
|
+
| { [group: string]: string | string[] }
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
If it's an object, each key is a group name, and each value is a
|
|
80
|
+
path, glob, or array of paths and globs, which specify the
|
|
81
|
+
location of the workspace projects. Glob matches are only
|
|
82
|
+
considered if they are a directory containing a `package.json`
|
|
83
|
+
file.
|
|
84
|
+
|
|
85
|
+
A `string` or `string[]` in the file is interpreted as
|
|
86
|
+
`{"packages": <value>}`. Ie, the default group is `packages`.
|
|
87
|
+
|
|
88
|
+
Workspaces are allowed to be in multiple groups, so something
|
|
89
|
+
like this is fine:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"apps": "apps/*",
|
|
94
|
+
"frontend": ["utils/frontend", "apps/website"],
|
|
95
|
+
"utils": ["utils/**"]
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
If the glob patterns result in a situation where one workspace
|
|
100
|
+
folder is contained within another workspace folder, an error
|
|
101
|
+
will be raised.
|
|
102
|
+
|
|
103
|
+
## Run Order
|
|
104
|
+
|
|
105
|
+
The `monorepo.run` method can run an async function for each
|
|
106
|
+
workspace in the project, visiting each exactly once.
|
|
107
|
+
|
|
108
|
+
When workspaces depend on one another, it will walk dependencies
|
|
109
|
+
before dependents, _unless_ there is a cycle, as that would be
|
|
110
|
+
impossible, and vlt does not prevent workspace dependency cycles.
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { DepID } from '@vltpkg/dep-id';
|
|
2
|
+
import { PackageJson } from '@vltpkg/package-json';
|
|
3
|
+
import { Manifest } from '@vltpkg/types';
|
|
4
|
+
import { DepResults } from 'graph-run';
|
|
5
|
+
import { PathScurry } from 'path-scurry';
|
|
6
|
+
export type WorkspacesLoadedConfig = {
|
|
7
|
+
workspace?: string[];
|
|
8
|
+
'workspace-group'?: string[];
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* The object passed to the constructor or {@link Monorepo#load} to limit which
|
|
12
|
+
* {@link Workspace Workspaces} get loaded.
|
|
13
|
+
*/
|
|
14
|
+
export type LoadQuery = {
|
|
15
|
+
/**
|
|
16
|
+
* A glob pattern string, or an array of them. Only workspaces found
|
|
17
|
+
* in paths matched will be loaded.
|
|
18
|
+
*/
|
|
19
|
+
paths?: string[] | string;
|
|
20
|
+
/**
|
|
21
|
+
* A string, or an array of strings. If set, only workspaces in the
|
|
22
|
+
* specified groups named will be included, if set.
|
|
23
|
+
*/
|
|
24
|
+
groups?: string[] | string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Canonical form of the {@link WorkspaceConfig}, used
|
|
28
|
+
* internally for consistency.
|
|
29
|
+
*/
|
|
30
|
+
export type WorkspaceConfigObject = Record<string, string[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Allowed datatype in the `vlt-workspaces.json` file.
|
|
33
|
+
*/
|
|
34
|
+
export type WorkspaceConfig = string[] | WorkspaceConfigObject | string;
|
|
35
|
+
/**
|
|
36
|
+
* Turn a {@link WorkspaceConfig} into a
|
|
37
|
+
* {@link WorkspaceConfigObject}, or throw if it's not valid.
|
|
38
|
+
*/
|
|
39
|
+
export declare const asWSConfig: (conf: unknown, path?: string) => WorkspaceConfigObject;
|
|
40
|
+
/**
|
|
41
|
+
* Throw if the provided value is not a valid {@link WorkspaceConfig}
|
|
42
|
+
*/
|
|
43
|
+
export declare const assertWSConfig: (conf: unknown, path?: string) => asserts conf is WorkspaceConfig;
|
|
44
|
+
export type MonorepoOptions = {
|
|
45
|
+
/**
|
|
46
|
+
* A {@link PackageJson} object, for sharing manifest caches
|
|
47
|
+
*/
|
|
48
|
+
packageJson?: PackageJson;
|
|
49
|
+
/**
|
|
50
|
+
* A {@link PathScurry} object, for use in globs
|
|
51
|
+
*/
|
|
52
|
+
scurry?: PathScurry;
|
|
53
|
+
/** Parsed normalized contents of a `vlt-workspaces.json` file */
|
|
54
|
+
config?: WorkspaceConfigObject;
|
|
55
|
+
/**
|
|
56
|
+
* If set, then {@link Monorepo#load} will be called immediately with
|
|
57
|
+
* this argument.
|
|
58
|
+
*/
|
|
59
|
+
load?: LoadQuery;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Class representing a Monorepo containing multiple workspaces.
|
|
63
|
+
*
|
|
64
|
+
* Does not automatically look up the root, but that can be provided by
|
|
65
|
+
* running `Config.load()`, since it stops seeking the route when a
|
|
66
|
+
* `vlt-workspaces.json` file is encountered.
|
|
67
|
+
*/
|
|
68
|
+
export declare class Monorepo {
|
|
69
|
+
#private;
|
|
70
|
+
/** The project root where vlt-workspaces.json is found */
|
|
71
|
+
projectRoot: string;
|
|
72
|
+
/** Scurry object to cache all filesystem calls (mostly globs) */
|
|
73
|
+
scurry: PathScurry;
|
|
74
|
+
packageJson: PackageJson;
|
|
75
|
+
/**
|
|
76
|
+
* Number of {@link Workspace} objects loaded in this Monorepo
|
|
77
|
+
*/
|
|
78
|
+
get size(): number;
|
|
79
|
+
constructor(projectRoot: string, options?: MonorepoOptions);
|
|
80
|
+
/**
|
|
81
|
+
* Load the workspace definitions from vlt-workspaces.json,
|
|
82
|
+
* canonicalizing the result into the effective `{[group:string]:string[]}`
|
|
83
|
+
* form.
|
|
84
|
+
*
|
|
85
|
+
* Eg:
|
|
86
|
+
* - `"src/*"` => `{packages:["src/*"]}`
|
|
87
|
+
* - `{"apps": "src/*"}` => `{apps: ["src/*"]}`
|
|
88
|
+
*/
|
|
89
|
+
get config(): WorkspaceConfigObject;
|
|
90
|
+
/**
|
|
91
|
+
* Iterating the Monorepo object yields the workspace objects, in as close to
|
|
92
|
+
* topological dependency order as possible.
|
|
93
|
+
*/
|
|
94
|
+
[Symbol.iterator](): Generator<Workspace, void, void>;
|
|
95
|
+
/**
|
|
96
|
+
* Iterating the Monorepo object yields the workspace objects, in as close to
|
|
97
|
+
* topological dependency order as possible.
|
|
98
|
+
*/
|
|
99
|
+
[Symbol.asyncIterator](): AsyncGenerator<Workspace, void, void>;
|
|
100
|
+
/**
|
|
101
|
+
* By default, loads all workspaces reachable in the Monorepo.
|
|
102
|
+
*
|
|
103
|
+
* If provided with one (`string`)or more (`string[]`) group names in
|
|
104
|
+
* the {@link LoadQuery#groups} field, then only Workspaces in the named
|
|
105
|
+
* group(s) will be considered. Note that group names are unique string
|
|
106
|
+
* matches, not globs.
|
|
107
|
+
*
|
|
108
|
+
* If provided with a set of arbitrary path arguments, then only paths
|
|
109
|
+
* patching the provided pattern(s) will be included.
|
|
110
|
+
*
|
|
111
|
+
* These two options intersect, so
|
|
112
|
+
* `load({groups:'foo', paths:'./foo/[xy]*'})` will only load the workspaces
|
|
113
|
+
* in the group `foo` that match the paths glob.
|
|
114
|
+
*/
|
|
115
|
+
load(query?: LoadQuery): this;
|
|
116
|
+
/**
|
|
117
|
+
* Return the array of workspace dependencies that are found in
|
|
118
|
+
* the loaded set, for use in calculating dependency graph order for
|
|
119
|
+
* build operations.
|
|
120
|
+
*
|
|
121
|
+
* This does *not* get the full set of dependencies, or expand any
|
|
122
|
+
* `workspace:` dependencies that are not loaded.
|
|
123
|
+
*
|
|
124
|
+
* Call with the `forceLoad` param set to `true` to attempt a full
|
|
125
|
+
* load if any deps are not currently loaded.
|
|
126
|
+
*/
|
|
127
|
+
getDeps(ws: Workspace, forceLoad?: boolean): Workspace[];
|
|
128
|
+
onCycle(_ws: Workspace, _cycle: Workspace[], _depPath: Workspace[]): void;
|
|
129
|
+
/**
|
|
130
|
+
* Return the set of workspaces in the named group.
|
|
131
|
+
* If the group is not one we know about, then undefined is returned.
|
|
132
|
+
*/
|
|
133
|
+
group(group: string): Set<Workspace> | undefined;
|
|
134
|
+
/**
|
|
135
|
+
* Get a loaded workspace by path or name.
|
|
136
|
+
*
|
|
137
|
+
* Note that this can only return workspaces that were ingested via a
|
|
138
|
+
* previous call to {@link Monorepo#load}.
|
|
139
|
+
*/
|
|
140
|
+
get(nameOrPath: string): Workspace | undefined;
|
|
141
|
+
/**
|
|
142
|
+
* get the list of all loaded workspace names used as keys
|
|
143
|
+
*/
|
|
144
|
+
names(): Generator<string, void, unknown>;
|
|
145
|
+
/**
|
|
146
|
+
* get the list of all loaded workspace paths used as keys
|
|
147
|
+
*/
|
|
148
|
+
paths(): Generator<string, void, unknown>;
|
|
149
|
+
/**
|
|
150
|
+
* get the workspace objects in no particular order.
|
|
151
|
+
* this is ever so slightly faster than iterating, because it doesn't
|
|
152
|
+
* explore the graph to yield results in topological dependency order,
|
|
153
|
+
* and should be used instead when order doesn't matter.
|
|
154
|
+
*/
|
|
155
|
+
values(): Generator<Workspace, void, unknown>;
|
|
156
|
+
/**
|
|
157
|
+
* Get all the keys (package names and paths) for loaded workspaces.
|
|
158
|
+
* Union of {@link Monorepo#names} and {@link Monorepo#paths}
|
|
159
|
+
*/
|
|
160
|
+
keys(): Generator<string, void, unknown>;
|
|
161
|
+
/**
|
|
162
|
+
* Filter the monorepo object yielding the workspace objects that matches
|
|
163
|
+
* either of the {@link WorkspacesLoadedConfig} options provided, in as close
|
|
164
|
+
* to topological dependency order as possible.
|
|
165
|
+
*/
|
|
166
|
+
filter({ workspace: namesOrPaths, 'workspace-group': groupName, }: WorkspacesLoadedConfig): Generator<Workspace, void, unknown>;
|
|
167
|
+
/**
|
|
168
|
+
* Run an operation asynchronously over all loaded workspaces
|
|
169
|
+
*
|
|
170
|
+
* If the `forceLoad` param is true, then it will attempt to do a full load
|
|
171
|
+
* when encountering a `workspace:` dependency that isn't loaded.
|
|
172
|
+
*
|
|
173
|
+
* Note that because the return type appears in the parameters of the
|
|
174
|
+
* operation function, it must be set explicitly either in the operation
|
|
175
|
+
* function signature or by calling `run<MyType>` or it'll fall back to
|
|
176
|
+
* `unknown`, similar to `Array.reduce()`, and for the same reason.
|
|
177
|
+
*/
|
|
178
|
+
run<R>(operation: (s: Workspace, signal: AbortSignal, depResults: DepResults<Workspace, R>) => Promise<R> | R, forceLoad?: boolean): Promise<Map<Workspace, R>>;
|
|
179
|
+
/**
|
|
180
|
+
* Run an operation synchronously over all loaded workspaces
|
|
181
|
+
*
|
|
182
|
+
* If the `forceLoad` param is true, then it will attempt to do a full load
|
|
183
|
+
* when encountering a `workspace:` dependency that isn't loaded.
|
|
184
|
+
*
|
|
185
|
+
* Note that because the return type appears in the parameters of the
|
|
186
|
+
* operation function, it must be set explicitly either in the operation
|
|
187
|
+
* function signature or by calling `runSync<MyType>` or it'll fall back to
|
|
188
|
+
* `unknown`, similar to `Array.reduce()`, and for the same reason.
|
|
189
|
+
*/
|
|
190
|
+
runSync<R>(operation: (s: Workspace, signal: AbortSignal, depResults: DepResults<Workspace, R>) => R, forceLoad?: boolean): Map<Workspace, R>;
|
|
191
|
+
/**
|
|
192
|
+
* Convenience method to instantiate and load in one call.
|
|
193
|
+
* Returns undefined if the project is not a monorepo workspaces
|
|
194
|
+
* root, otherwise returns the loaded Monorepo.
|
|
195
|
+
*/
|
|
196
|
+
static maybeLoad(projectRoot: string, options?: MonorepoOptions): Monorepo | undefined;
|
|
197
|
+
/**
|
|
198
|
+
* Convenience method to instantiate and load in one call.
|
|
199
|
+
* Throws if called on a directory that is not a workspaces root.
|
|
200
|
+
*/
|
|
201
|
+
static load(projectRoot: string, options?: MonorepoOptions): Monorepo;
|
|
202
|
+
}
|
|
203
|
+
export declare const workspaceCache: Map<string, Workspace>;
|
|
204
|
+
/**
|
|
205
|
+
* Class representing a single Workspace in a {@link Monorepo}
|
|
206
|
+
*/
|
|
207
|
+
export declare class Workspace {
|
|
208
|
+
#private;
|
|
209
|
+
id: DepID;
|
|
210
|
+
path: string;
|
|
211
|
+
fullpath: string;
|
|
212
|
+
manifest: Manifest;
|
|
213
|
+
groups: string[];
|
|
214
|
+
name: string;
|
|
215
|
+
constructor(path: string, manifest: Manifest, fullpath: string);
|
|
216
|
+
get keys(): string[];
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
import { joinDepIDTuple } from '@vltpkg/dep-id';
|
|
2
|
+
import { error } from '@vltpkg/error-cause';
|
|
3
|
+
import { PackageJson } from '@vltpkg/package-json';
|
|
4
|
+
import { readFileSync, statSync } from 'fs';
|
|
5
|
+
import { globSync } from 'glob';
|
|
6
|
+
import { graphRun, graphRunSync } from 'graph-run';
|
|
7
|
+
import { basename, resolve, posix } from 'path';
|
|
8
|
+
import { PathScurry } from 'path-scurry';
|
|
9
|
+
import { parse } from 'polite-json';
|
|
10
|
+
import { minimatch } from 'minimatch';
|
|
11
|
+
/**
|
|
12
|
+
* Turn a {@link WorkspaceConfig} into a
|
|
13
|
+
* {@link WorkspaceConfigObject}, or throw if it's not valid.
|
|
14
|
+
*/
|
|
15
|
+
export const asWSConfig = (conf, path) => {
|
|
16
|
+
assertWSConfig(conf, path);
|
|
17
|
+
return (typeof conf === 'string' ? { packages: [conf] }
|
|
18
|
+
: Array.isArray(conf) ? { packages: conf }
|
|
19
|
+
: conf);
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Throw if the provided value is not a valid {@link WorkspaceConfig}
|
|
23
|
+
*/
|
|
24
|
+
export const assertWSConfig = (conf, path) => {
|
|
25
|
+
if (typeof conf === 'string')
|
|
26
|
+
return conf;
|
|
27
|
+
if (Array.isArray(conf)) {
|
|
28
|
+
for (const c of conf) {
|
|
29
|
+
if (typeof c !== 'string') {
|
|
30
|
+
throw error('Invalid workspace definition', {
|
|
31
|
+
path,
|
|
32
|
+
found: c,
|
|
33
|
+
wanted: 'string',
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (conf && typeof conf === 'object') {
|
|
40
|
+
for (const [group, value] of Object.entries(conf)) {
|
|
41
|
+
if (typeof value === 'string')
|
|
42
|
+
continue;
|
|
43
|
+
if (Array.isArray(value)) {
|
|
44
|
+
for (const c of value) {
|
|
45
|
+
if (typeof c !== 'string') {
|
|
46
|
+
throw error('Invalid workspace definition', {
|
|
47
|
+
path,
|
|
48
|
+
name: group,
|
|
49
|
+
found: c,
|
|
50
|
+
wanted: 'string',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
throw error('Invalid workspace definition', {
|
|
57
|
+
path,
|
|
58
|
+
name: group,
|
|
59
|
+
found: value,
|
|
60
|
+
wanted: 'string | string[]',
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
throw error('Invalid workspace definition', {
|
|
66
|
+
path,
|
|
67
|
+
found: conf,
|
|
68
|
+
wanted: 'string | string[] | { [group: string]: string | string[] }',
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Class representing a Monorepo containing multiple workspaces.
|
|
73
|
+
*
|
|
74
|
+
* Does not automatically look up the root, but that can be provided by
|
|
75
|
+
* running `Config.load()`, since it stops seeking the route when a
|
|
76
|
+
* `vlt-workspaces.json` file is encountered.
|
|
77
|
+
*/
|
|
78
|
+
export class Monorepo {
|
|
79
|
+
/** The project root where vlt-workspaces.json is found */
|
|
80
|
+
projectRoot;
|
|
81
|
+
/** Scurry object to cache all filesystem calls (mostly globs) */
|
|
82
|
+
scurry;
|
|
83
|
+
// maps both name and path to the workspace objects
|
|
84
|
+
#workspaces = new Map();
|
|
85
|
+
#groups = new Map();
|
|
86
|
+
#config;
|
|
87
|
+
packageJson;
|
|
88
|
+
/**
|
|
89
|
+
* Number of {@link Workspace} objects loaded in this Monorepo
|
|
90
|
+
*/
|
|
91
|
+
get size() {
|
|
92
|
+
return [...this.values()].length;
|
|
93
|
+
}
|
|
94
|
+
constructor(projectRoot, options = {}) {
|
|
95
|
+
this.projectRoot = resolve(projectRoot);
|
|
96
|
+
this.scurry = options.scurry ?? new PathScurry(projectRoot);
|
|
97
|
+
this.packageJson = options.packageJson ?? new PackageJson();
|
|
98
|
+
this.#config = options.config;
|
|
99
|
+
if (options.load)
|
|
100
|
+
this.load(options.load);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Load the workspace definitions from vlt-workspaces.json,
|
|
104
|
+
* canonicalizing the result into the effective `{[group:string]:string[]}`
|
|
105
|
+
* form.
|
|
106
|
+
*
|
|
107
|
+
* Eg:
|
|
108
|
+
* - `"src/*"` => `{packages:["src/*"]}`
|
|
109
|
+
* - `{"apps": "src/*"}` => `{apps: ["src/*"]}`
|
|
110
|
+
*/
|
|
111
|
+
get config() {
|
|
112
|
+
if (this.#config)
|
|
113
|
+
return this.#config;
|
|
114
|
+
const file = resolve(this.projectRoot, 'vlt-workspaces.json');
|
|
115
|
+
let confData;
|
|
116
|
+
try {
|
|
117
|
+
confData = readFileSync(file, 'utf8');
|
|
118
|
+
}
|
|
119
|
+
catch (er) {
|
|
120
|
+
throw error('Not in a monorepo, no vlt-workspaces.json found', {
|
|
121
|
+
path: this.projectRoot,
|
|
122
|
+
cause: er,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
let parsed;
|
|
126
|
+
try {
|
|
127
|
+
parsed = parse(confData);
|
|
128
|
+
}
|
|
129
|
+
catch (er) {
|
|
130
|
+
throw error('Invalid vlt-workspaces.json file', {
|
|
131
|
+
path: this.projectRoot,
|
|
132
|
+
cause: er,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
this.#config = asWSConfig(parsed, file);
|
|
136
|
+
return this.#config;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Iterating the Monorepo object yields the workspace objects, in as close to
|
|
140
|
+
* topological dependency order as possible.
|
|
141
|
+
*/
|
|
142
|
+
*[Symbol.iterator]() {
|
|
143
|
+
const [ws] = [...this.values()];
|
|
144
|
+
if (!ws)
|
|
145
|
+
return;
|
|
146
|
+
// leverage the fact that graphRun returns results in
|
|
147
|
+
// as close to topological order as possible.
|
|
148
|
+
for (const workspace of this.runSync(() => { }).keys()) {
|
|
149
|
+
yield workspace;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Iterating the Monorepo object yields the workspace objects, in as close to
|
|
154
|
+
* topological dependency order as possible.
|
|
155
|
+
*/
|
|
156
|
+
async *[Symbol.asyncIterator]() {
|
|
157
|
+
const [ws] = [...this.values()];
|
|
158
|
+
if (!ws)
|
|
159
|
+
return;
|
|
160
|
+
for (const workspace of (await this.run(() => { })).keys()) {
|
|
161
|
+
yield workspace;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* By default, loads all workspaces reachable in the Monorepo.
|
|
166
|
+
*
|
|
167
|
+
* If provided with one (`string`)or more (`string[]`) group names in
|
|
168
|
+
* the {@link LoadQuery#groups} field, then only Workspaces in the named
|
|
169
|
+
* group(s) will be considered. Note that group names are unique string
|
|
170
|
+
* matches, not globs.
|
|
171
|
+
*
|
|
172
|
+
* If provided with a set of arbitrary path arguments, then only paths
|
|
173
|
+
* patching the provided pattern(s) will be included.
|
|
174
|
+
*
|
|
175
|
+
* These two options intersect, so
|
|
176
|
+
* `load({groups:'foo', paths:'./foo/[xy]*'})` will only load the workspaces
|
|
177
|
+
* in the group `foo` that match the paths glob.
|
|
178
|
+
*/
|
|
179
|
+
load(query = {}) {
|
|
180
|
+
const paths = new Set(typeof query.paths === 'string' ?
|
|
181
|
+
[query.paths]
|
|
182
|
+
: (query.paths ?? []));
|
|
183
|
+
const groups = new Set(typeof query.groups === 'string' ?
|
|
184
|
+
[query.groups]
|
|
185
|
+
: (query.groups ?? []));
|
|
186
|
+
const groupsExpanded = {};
|
|
187
|
+
for (const [group, pattern] of Object.entries(this.config)) {
|
|
188
|
+
if (groups.size && !groups.has(group))
|
|
189
|
+
continue;
|
|
190
|
+
groupsExpanded[group] = this.#glob(pattern);
|
|
191
|
+
}
|
|
192
|
+
const filter = paths.size ? this.#glob([...paths]) : paths;
|
|
193
|
+
// if we specified paths, but none matched, nothing to do
|
|
194
|
+
if (paths.size && !filter.size)
|
|
195
|
+
return this;
|
|
196
|
+
for (const [group, matches] of Object.entries(groupsExpanded)) {
|
|
197
|
+
for (const path of matches) {
|
|
198
|
+
if (filter.size && !filter.has(path))
|
|
199
|
+
continue;
|
|
200
|
+
this.#loadWS(path, group);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return this;
|
|
204
|
+
}
|
|
205
|
+
// Either load a workspace from disk, or from our internal set,
|
|
206
|
+
// and assign it to the named group
|
|
207
|
+
#loadWS(path, group) {
|
|
208
|
+
const fullpath = resolve(this.projectRoot, path);
|
|
209
|
+
const loaded = this.#workspaces.get(fullpath);
|
|
210
|
+
if (loaded)
|
|
211
|
+
return loaded;
|
|
212
|
+
const fromCache = workspaceCache.get(fullpath);
|
|
213
|
+
const manifest = fromCache?.manifest ?? this.packageJson.read(fullpath);
|
|
214
|
+
const ws = fromCache ?? new Workspace(path, manifest, fullpath);
|
|
215
|
+
if (group)
|
|
216
|
+
ws.groups.push(group);
|
|
217
|
+
this.#workspaces.set(ws.fullpath, ws);
|
|
218
|
+
this.#workspaces.set(ws.path, ws);
|
|
219
|
+
this.#workspaces.set(ws.name, ws);
|
|
220
|
+
for (const name of ws.groups) {
|
|
221
|
+
const group = this.#groups.get(name) ?? new Set();
|
|
222
|
+
group.add(ws);
|
|
223
|
+
this.#groups.set(name, group);
|
|
224
|
+
}
|
|
225
|
+
return ws;
|
|
226
|
+
}
|
|
227
|
+
// can't be cached, because it's dependent on the matches set
|
|
228
|
+
// but still worthwhile to have it defined in one place
|
|
229
|
+
#globOptions(matches) {
|
|
230
|
+
// if the entry or any of its parent dirs are already matched,
|
|
231
|
+
// then we should not explore further down that directory tree.
|
|
232
|
+
// if we hit the projectRoot then stop searching.
|
|
233
|
+
const inMatches = (p) => {
|
|
234
|
+
return (!!p?.relativePosix() &&
|
|
235
|
+
(matches.has(p.relativePosix()) || inMatches(p.parent)));
|
|
236
|
+
};
|
|
237
|
+
return {
|
|
238
|
+
root: this.projectRoot,
|
|
239
|
+
cwd: this.projectRoot,
|
|
240
|
+
posix: true,
|
|
241
|
+
scurry: this.scurry,
|
|
242
|
+
withFileTypes: false,
|
|
243
|
+
ignore: {
|
|
244
|
+
childrenIgnored: p => basename(p.relativePosix()) === 'node_modules' ||
|
|
245
|
+
inMatches(p),
|
|
246
|
+
// ignore if fails to load package.json
|
|
247
|
+
ignored: p => {
|
|
248
|
+
p.lstatSync();
|
|
249
|
+
const rel = p.relativePosix();
|
|
250
|
+
if (!rel)
|
|
251
|
+
return true;
|
|
252
|
+
const maybeDelete = [];
|
|
253
|
+
for (const m of matches) {
|
|
254
|
+
if (rel.startsWith(m + '/'))
|
|
255
|
+
return true;
|
|
256
|
+
if (m.startsWith(rel + '/')) {
|
|
257
|
+
maybeDelete.push(m);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (!p.isDirectory())
|
|
261
|
+
return true;
|
|
262
|
+
const pj = p.resolve('package.json').lstatSync();
|
|
263
|
+
if (!pj?.isFile())
|
|
264
|
+
return true;
|
|
265
|
+
try {
|
|
266
|
+
this.packageJson.read(p.fullpath());
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
for (const m of maybeDelete) {
|
|
272
|
+
matches.delete(m);
|
|
273
|
+
}
|
|
274
|
+
matches.add(rel);
|
|
275
|
+
return false;
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
#glob(pattern) {
|
|
281
|
+
const matches = new Set();
|
|
282
|
+
globSync(pattern, this.#globOptions(matches));
|
|
283
|
+
return matches;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Return the array of workspace dependencies that are found in
|
|
287
|
+
* the loaded set, for use in calculating dependency graph order for
|
|
288
|
+
* build operations.
|
|
289
|
+
*
|
|
290
|
+
* This does *not* get the full set of dependencies, or expand any
|
|
291
|
+
* `workspace:` dependencies that are not loaded.
|
|
292
|
+
*
|
|
293
|
+
* Call with the `forceLoad` param set to `true` to attempt a full
|
|
294
|
+
* load if any deps are not currently loaded.
|
|
295
|
+
*/
|
|
296
|
+
getDeps(ws, forceLoad = false) {
|
|
297
|
+
// load manifest and find workspace: deps
|
|
298
|
+
// filter by those loaded
|
|
299
|
+
const { manifest } = ws;
|
|
300
|
+
const depWorkspaces = [];
|
|
301
|
+
let didForceLoad = false;
|
|
302
|
+
for (const depType of [
|
|
303
|
+
'dependencies',
|
|
304
|
+
'devDependencies',
|
|
305
|
+
'optionalDependencies',
|
|
306
|
+
'peerDependencies',
|
|
307
|
+
]) {
|
|
308
|
+
const deps = manifest[depType];
|
|
309
|
+
if (!deps)
|
|
310
|
+
continue;
|
|
311
|
+
for (const [dep, spec] of Object.entries(deps)) {
|
|
312
|
+
if (spec.startsWith('workspace:')) {
|
|
313
|
+
let depWS = this.#workspaces.get(dep);
|
|
314
|
+
if (!depWS) {
|
|
315
|
+
if (!forceLoad)
|
|
316
|
+
continue;
|
|
317
|
+
if (didForceLoad)
|
|
318
|
+
continue;
|
|
319
|
+
didForceLoad = true;
|
|
320
|
+
this.load();
|
|
321
|
+
depWS = this.#workspaces.get(dep);
|
|
322
|
+
if (!depWS)
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
depWorkspaces.push(depWS);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return depWorkspaces;
|
|
330
|
+
}
|
|
331
|
+
onCycle(_ws, _cycle, _depPath) {
|
|
332
|
+
// XXX - process logging? Need to say something like:
|
|
333
|
+
// Cyclical workspace dependency warning!
|
|
334
|
+
// When evaluating dependency ${ws.name} via ${
|
|
335
|
+
// path.map(ws => ws.name).join(' -> ')
|
|
336
|
+
// }, a dependency cycle was detected: ${
|
|
337
|
+
// cycle.map(ws => ws.name).join(' -> ')
|
|
338
|
+
// }. Operation will continue, but dependency order not guaranteed.`
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Return the set of workspaces in the named group.
|
|
342
|
+
* If the group is not one we know about, then undefined is returned.
|
|
343
|
+
*/
|
|
344
|
+
group(group) {
|
|
345
|
+
return this.#groups.get(group);
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Get a loaded workspace by path or name.
|
|
349
|
+
*
|
|
350
|
+
* Note that this can only return workspaces that were ingested via a
|
|
351
|
+
* previous call to {@link Monorepo#load}.
|
|
352
|
+
*/
|
|
353
|
+
get(nameOrPath) {
|
|
354
|
+
return this.#workspaces.get(nameOrPath);
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* get the list of all loaded workspace names used as keys
|
|
358
|
+
*/
|
|
359
|
+
*names() {
|
|
360
|
+
for (const [key, ws] of this.#workspaces) {
|
|
361
|
+
if (key === ws.name)
|
|
362
|
+
yield key;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* get the list of all loaded workspace paths used as keys
|
|
367
|
+
*/
|
|
368
|
+
*paths() {
|
|
369
|
+
for (const [key, ws] of this.#workspaces) {
|
|
370
|
+
if (key === ws.path)
|
|
371
|
+
yield key;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* get the workspace objects in no particular order.
|
|
376
|
+
* this is ever so slightly faster than iterating, because it doesn't
|
|
377
|
+
* explore the graph to yield results in topological dependency order,
|
|
378
|
+
* and should be used instead when order doesn't matter.
|
|
379
|
+
*/
|
|
380
|
+
*values() {
|
|
381
|
+
const seen = new Set();
|
|
382
|
+
for (const ws of this.#workspaces.values()) {
|
|
383
|
+
if (seen.has(ws.fullpath))
|
|
384
|
+
continue;
|
|
385
|
+
seen.add(ws.fullpath);
|
|
386
|
+
yield ws;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Get all the keys (package names and paths) for loaded workspaces.
|
|
391
|
+
* Union of {@link Monorepo#names} and {@link Monorepo#paths}
|
|
392
|
+
*/
|
|
393
|
+
*keys() {
|
|
394
|
+
for (const ws of this.values()) {
|
|
395
|
+
yield ws.path;
|
|
396
|
+
if (ws.name !== ws.path)
|
|
397
|
+
yield ws.name;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Filter the monorepo object yielding the workspace objects that matches
|
|
402
|
+
* either of the {@link WorkspacesLoadedConfig} options provided, in as close
|
|
403
|
+
* to topological dependency order as possible.
|
|
404
|
+
*/
|
|
405
|
+
*filter({ workspace: namesOrPaths, 'workspace-group': groupName, }) {
|
|
406
|
+
const globPatternChecks = namesOrPaths?.map(glob => minimatch.filter(posix.join(glob)));
|
|
407
|
+
for (const ws of this) {
|
|
408
|
+
// check if any group has any of the provided group names
|
|
409
|
+
if (groupName?.some(i => ws.groups.includes(i))) {
|
|
410
|
+
yield ws;
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
// check if any workspace-provided name directly matches any of the
|
|
414
|
+
// configured workspaces by either name or file path
|
|
415
|
+
if (namesOrPaths
|
|
416
|
+
?.map(i => posix.join(i))
|
|
417
|
+
.some(i => ws.keys.includes(i))) {
|
|
418
|
+
yield ws;
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
// check if one of the workspace values are matching glob patterns
|
|
422
|
+
if (ws.keys.some(key => globPatternChecks?.some(fn => fn(key)))) {
|
|
423
|
+
yield ws;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Run an operation asynchronously over all loaded workspaces
|
|
429
|
+
*
|
|
430
|
+
* If the `forceLoad` param is true, then it will attempt to do a full load
|
|
431
|
+
* when encountering a `workspace:` dependency that isn't loaded.
|
|
432
|
+
*
|
|
433
|
+
* Note that because the return type appears in the parameters of the
|
|
434
|
+
* operation function, it must be set explicitly either in the operation
|
|
435
|
+
* function signature or by calling `run<MyType>` or it'll fall back to
|
|
436
|
+
* `unknown`, similar to `Array.reduce()`, and for the same reason.
|
|
437
|
+
*/
|
|
438
|
+
async run(operation, forceLoad = false) {
|
|
439
|
+
const [ws, ...rest] = [...this.#workspaces.values()];
|
|
440
|
+
if (!ws) {
|
|
441
|
+
throw error('No workspaces loaded', undefined, this.run);
|
|
442
|
+
}
|
|
443
|
+
return graphRun({
|
|
444
|
+
graph: [ws, ...rest],
|
|
445
|
+
getDeps: ws => this.getDeps(ws, forceLoad),
|
|
446
|
+
visit: async (ws, signal, _, depResults) => await operation(ws, signal, depResults),
|
|
447
|
+
onCycle: (ws, cycle, path) => this.onCycle(ws, cycle, path),
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Run an operation synchronously over all loaded workspaces
|
|
452
|
+
*
|
|
453
|
+
* If the `forceLoad` param is true, then it will attempt to do a full load
|
|
454
|
+
* when encountering a `workspace:` dependency that isn't loaded.
|
|
455
|
+
*
|
|
456
|
+
* Note that because the return type appears in the parameters of the
|
|
457
|
+
* operation function, it must be set explicitly either in the operation
|
|
458
|
+
* function signature or by calling `runSync<MyType>` or it'll fall back to
|
|
459
|
+
* `unknown`, similar to `Array.reduce()`, and for the same reason.
|
|
460
|
+
*/
|
|
461
|
+
runSync(operation, forceLoad = false) {
|
|
462
|
+
const [ws, ...rest] = [...this.#workspaces.values()];
|
|
463
|
+
if (!ws) {
|
|
464
|
+
throw error('No workspaces loaded', undefined, this.run);
|
|
465
|
+
}
|
|
466
|
+
return graphRunSync({
|
|
467
|
+
graph: [ws, ...rest],
|
|
468
|
+
getDeps: ws => this.getDeps(ws, forceLoad),
|
|
469
|
+
visit: (ws, signal, _, depResults) => operation(ws, signal, depResults),
|
|
470
|
+
onCycle: (ws, cycle, path) => this.onCycle(ws, cycle, path),
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Convenience method to instantiate and load in one call.
|
|
475
|
+
* Returns undefined if the project is not a monorepo workspaces
|
|
476
|
+
* root, otherwise returns the loaded Monorepo.
|
|
477
|
+
*/
|
|
478
|
+
static maybeLoad(projectRoot, options = { load: {} }) {
|
|
479
|
+
try {
|
|
480
|
+
if (!statSync(resolve(projectRoot, 'vlt-workspaces.json')).isFile()) {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
catch {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
const { load = {} } = options;
|
|
488
|
+
return new Monorepo(projectRoot, { ...options, load });
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Convenience method to instantiate and load in one call.
|
|
492
|
+
* Throws if called on a directory that is not a workspaces root.
|
|
493
|
+
*/
|
|
494
|
+
static load(projectRoot, options = { load: {} }) {
|
|
495
|
+
const { load = {} } = options;
|
|
496
|
+
return new Monorepo(projectRoot, { ...options, load });
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
export const workspaceCache = new Map();
|
|
500
|
+
/**
|
|
501
|
+
* Class representing a single Workspace in a {@link Monorepo}
|
|
502
|
+
*/
|
|
503
|
+
export class Workspace {
|
|
504
|
+
id;
|
|
505
|
+
path;
|
|
506
|
+
fullpath;
|
|
507
|
+
manifest;
|
|
508
|
+
groups = [];
|
|
509
|
+
name;
|
|
510
|
+
#keys;
|
|
511
|
+
constructor(path, manifest, fullpath) {
|
|
512
|
+
this.id = joinDepIDTuple(['workspace', path]);
|
|
513
|
+
workspaceCache.set(fullpath, this);
|
|
514
|
+
this.path = path;
|
|
515
|
+
this.fullpath = fullpath;
|
|
516
|
+
this.manifest = manifest;
|
|
517
|
+
this.name = manifest.name ?? path;
|
|
518
|
+
}
|
|
519
|
+
get keys() {
|
|
520
|
+
if (this.#keys) {
|
|
521
|
+
return this.#keys;
|
|
522
|
+
}
|
|
523
|
+
this.#keys = [this.name, this.path, this.fullpath];
|
|
524
|
+
return this.#keys;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACtD,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAElD,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAA;AAC3C,OAAO,EAAiC,QAAQ,EAAE,MAAM,MAAM,CAAA;AAC9D,OAAO,EAAc,QAAQ,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAC9D,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,MAAM,CAAA;AAC/C,OAAO,EAAQ,UAAU,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAsCrC;;;GAGG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CACxB,IAAa,EACb,IAAa,EACU,EAAE;IACzB,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IAC1B,OAAO,CACL,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE;QAC/C,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE;YAC1C,CAAC,CAAC,IAAI,CACP,CAAA;AACH,CAAC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAGY,CACrC,IAAa,EACb,IAAa,EACb,EAAE;IACF,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IAEzC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;gBAC1B,MAAM,KAAK,CAAC,8BAA8B,EAAE;oBAC1C,IAAI;oBACJ,KAAK,EAAE,CAAC;oBACR,MAAM,EAAE,QAAQ;iBACjB,CAAC,CAAA;YACJ,CAAC;QACH,CAAC;QACD,OAAM;IACR,CAAC;IAED,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QACrC,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YAClD,IAAI,OAAO,KAAK,KAAK,QAAQ;gBAAE,SAAQ;YACvC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;gBACzB,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;oBACtB,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;wBAC1B,MAAM,KAAK,CAAC,8BAA8B,EAAE;4BAC1C,IAAI;4BACJ,IAAI,EAAE,KAAK;4BACX,KAAK,EAAE,CAAC;4BACR,MAAM,EAAE,QAAQ;yBACjB,CAAC,CAAA;oBACJ,CAAC;gBACH,CAAC;gBACD,SAAQ;YACV,CAAC;YACD,MAAM,KAAK,CAAC,8BAA8B,EAAE;gBAC1C,IAAI;gBACJ,IAAI,EAAE,KAAK;gBACX,KAAK,EAAE,KAAK;gBACZ,MAAM,EAAE,mBAAmB;aAC5B,CAAC,CAAA;QACJ,CAAC;QACD,OAAM;IACR,CAAC;IAED,MAAM,KAAK,CAAC,8BAA8B,EAAE;QAC1C,IAAI;QACJ,KAAK,EAAE,IAAI;QACX,MAAM,EACJ,4DAA4D;KAC/D,CAAC,CAAA;AACJ,CAAC,CAAA;AAoBD;;;;;;GAMG;AACH,MAAM,OAAO,QAAQ;IACnB,0DAA0D;IAC1D,WAAW,CAAQ;IACnB,iEAAiE;IACjE,MAAM,CAAY;IAElB,mDAAmD;IACnD,WAAW,GAAG,IAAI,GAAG,EAAqB,CAAA;IAC1C,OAAO,GAAG,IAAI,GAAG,EAA0B,CAAA;IAC3C,OAAO,CAAwB;IAC/B,WAAW,CAAa;IAExB;;OAEG;IACH,IAAI,IAAI;QACN,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAA;IAClC,CAAC;IAED,YAAY,WAAmB,EAAE,UAA2B,EAAE;QAC5D,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;QACvC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,IAAI,UAAU,CAAC,WAAW,CAAC,CAAA;QAC3D,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,IAAI,WAAW,EAAE,CAAA;QAC3D,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,MAAM,CAAA;QAC7B,IAAI,OAAO,CAAC,IAAI;YAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3C,CAAC;IAED;;;;;;;;OAQG;IACH,IAAI,MAAM;QACR,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC,OAAO,CAAA;QACrC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,qBAAqB,CAAC,CAAA;QAC7D,IAAI,QAAgB,CAAA;QACpB,IAAI,CAAC;YACH,QAAQ,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;QACvC,CAAC;QAAC,OAAO,EAAE,EAAE,CAAC;YACZ,MAAM,KAAK,CAAC,iDAAiD,EAAE;gBAC7D,IAAI,EAAE,IAAI,CAAC,WAAW;gBACtB,KAAK,EAAE,EAAW;aACnB,CAAC,CAAA;QACJ,CAAC;QACD,IAAI,MAAe,CAAA;QACnB,IAAI,CAAC;YACH,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAA;QAC1B,CAAC;QAAC,OAAO,EAAE,EAAE,CAAC;YACZ,MAAM,KAAK,CAAC,kCAAkC,EAAE;gBAC9C,IAAI,EAAE,IAAI,CAAC,WAAW;gBACtB,KAAK,EAAE,EAAW;aACnB,CAAC,CAAA;QACJ,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;QACvC,OAAO,IAAI,CAAC,OAAO,CAAA;IACrB,CAAC;IAED;;;OAGG;IACH,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;QAChB,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,CAAA;QAC/B,IAAI,CAAC,EAAE;YAAE,OAAM;QACf,qDAAqD;QACrD,6CAA6C;QAC7C,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YACtD,MAAM,SAAS,CAAA;QACjB,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC;QAK3B,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,CAAA;QAC/B,IAAI,CAAC,EAAE;YAAE,OAAM;QACf,KAAK,MAAM,SAAS,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YAC1D,MAAM,SAAS,CAAA;QACjB,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACH,IAAI,CAAC,QAAmB,EAAE;QACxB,MAAM,KAAK,GAAG,IAAI,GAAG,CACnB,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC;YAC/B,CAAC,KAAK,CAAC,KAAK,CAAC;YACf,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,CACtB,CAAA;QACD,MAAM,MAAM,GAAG,IAAI,GAAG,CACpB,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC;YAChC,CAAC,KAAK,CAAC,MAAM,CAAC;YAChB,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,CACvB,CAAA;QAED,MAAM,cAAc,GAAgC,EAAE,CAAA;QACtD,KAAK,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3D,IAAI,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,SAAQ;YAC/C,cAAc,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QAC7C,CAAC;QACD,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;QAE1D,yDAAyD;QACzD,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI;YAAE,OAAO,IAAI,CAAA;QAE3C,KAAK,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC;YAC9D,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;gBAC3B,IAAI,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;oBAAE,SAAQ;gBAC9C,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;YAC3B,CAAC;QACH,CAAC;QAED,OAAO,IAAI,CAAA;IACb,CAAC;IAED,+DAA+D;IAC/D,mCAAmC;IACnC,OAAO,CAAC,IAAY,EAAE,KAAc;QAClC,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,CAAA;QAChD,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;QAC7C,IAAI,MAAM;YAAE,OAAO,MAAM,CAAA;QACzB,MAAM,SAAS,GAAG,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;QAC9C,MAAM,QAAQ,GACZ,SAAS,EAAE,QAAQ,IAAI,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QACxD,MAAM,EAAE,GAAG,SAAS,IAAI,IAAI,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAA;QAC/D,IAAI,KAAK;YAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAChC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAA;QACrC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;QACjC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;QACjC,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC;YAC7B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,EAAE,CAAA;YACjD,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;YACb,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;QAC/B,CAAC;QACD,OAAO,EAAE,CAAA;IACX,CAAC;IAED,6DAA6D;IAC7D,uDAAuD;IACvD,YAAY,CAAC,OAAoB;QAC/B,8DAA8D;QAC9D,+DAA+D;QAC/D,iDAAiD;QACjD,MAAM,SAAS,GAAG,CAAC,CAAQ,EAAW,EAAE;YACtC,OAAO,CACL,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE;gBACpB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CACxD,CAAA;QACH,CAAC,CAAA;QAED,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,WAAW;YACtB,GAAG,EAAE,IAAI,CAAC,WAAW;YACrB,KAAK,EAAE,IAAI;YACX,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,aAAa,EAAE,KAAK;YACpB,MAAM,EAAE;gBACN,eAAe,EAAE,CAAC,CAAC,EAAE,CACnB,QAAQ,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC,KAAK,cAAc;oBAC9C,SAAS,CAAC,CAAC,CAAC;gBACd,uCAAuC;gBACvC,OAAO,EAAE,CAAC,CAAC,EAAE;oBACX,CAAC,CAAC,SAAS,EAAE,CAAA;oBACb,MAAM,GAAG,GAAG,CAAC,CAAC,aAAa,EAAE,CAAA;oBAC7B,IAAI,CAAC,GAAG;wBAAE,OAAO,IAAI,CAAA;oBACrB,MAAM,WAAW,GAAa,EAAE,CAAA;oBAChC,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;wBACxB,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,GAAG,GAAG,CAAC;4BAAE,OAAO,IAAI,CAAA;wBACxC,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,GAAG,GAAG,CAAC,EAAE,CAAC;4BAC5B,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;wBACrB,CAAC;oBACH,CAAC;oBACD,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE;wBAAE,OAAO,IAAI,CAAA;oBACjC,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,SAAS,EAAE,CAAA;oBAChD,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE;wBAAE,OAAO,IAAI,CAAA;oBAC9B,IAAI,CAAC;wBACH,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAA;oBACrC,CAAC;oBAAC,MAAM,CAAC;wBACP,OAAO,IAAI,CAAA;oBACb,CAAC;oBACD,KAAK,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC;wBAC5B,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;oBACnB,CAAC;oBACD,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;oBAChB,OAAO,KAAK,CAAA;gBACd,CAAC;aACF;SACF,CAAA;IACH,CAAC;IAED,KAAK,CAAC,OAA0B;QAC9B,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAA;QACjC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAA;QAC7C,OAAO,OAAO,CAAA;IAChB,CAAC;IAED;;;;;;;;;;OAUG;IACH,OAAO,CAAC,EAAa,EAAE,SAAS,GAAG,KAAK;QACtC,yCAAyC;QACzC,yBAAyB;QACzB,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAA;QACvB,MAAM,aAAa,GAAgB,EAAE,CAAA;QACrC,IAAI,YAAY,GAAG,KAAK,CAAA;QACxB,KAAK,MAAM,OAAO,IAAI;YACpB,cAAc;YACd,iBAAiB;YACjB,sBAAsB;YACtB,kBAAkB;SACV,EAAE,CAAC;YACX,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAA;YAC9B,IAAI,CAAC,IAAI;gBAAE,SAAQ;YACnB,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC/C,IAAI,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;oBAClC,IAAI,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;oBACrC,IAAI,CAAC,KAAK,EAAE,CAAC;wBACX,IAAI,CAAC,SAAS;4BAAE,SAAQ;wBACxB,IAAI,YAAY;4BAAE,SAAQ;wBAC1B,YAAY,GAAG,IAAI,CAAA;wBACnB,IAAI,CAAC,IAAI,EAAE,CAAA;wBACX,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;wBACjC,IAAI,CAAC,KAAK;4BAAE,SAAQ;oBACtB,CAAC;oBACD,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;gBAC3B,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,aAAa,CAAA;IACtB,CAAC;IAED,OAAO,CACL,GAAc,EACd,MAAmB,EACnB,QAAqB;QAErB,qDAAqD;QACrD,yCAAyC;QACzC,+CAA+C;QAC/C,yCAAyC;QACzC,yCAAyC;QACzC,0CAA0C;QAC1C,oEAAoE;IACtE,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,KAAa;QACjB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;IAChC,CAAC;IAED;;;;;OAKG;IACH,GAAG,CAAC,UAAkB;QACpB,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;IACzC,CAAC;IAED;;OAEG;IACH,CAAC,KAAK;QACJ,KAAK,MAAM,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACzC,IAAI,GAAG,KAAK,EAAE,CAAC,IAAI;gBAAE,MAAM,GAAG,CAAA;QAChC,CAAC;IACH,CAAC;IAED;;OAEG;IACH,CAAC,KAAK;QACJ,KAAK,MAAM,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACzC,IAAI,GAAG,KAAK,EAAE,CAAC,IAAI;gBAAE,MAAM,GAAG,CAAA;QAChC,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,CAAC,MAAM;QACL,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;QAC9B,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,EAAE,CAAC;YAC3C,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC;gBAAE,SAAQ;YACnC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAA;YACrB,MAAM,EAAE,CAAA;QACV,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,CAAC,IAAI;QACH,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;YAC/B,MAAM,EAAE,CAAC,IAAI,CAAA;YACb,IAAI,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,IAAI;gBAAE,MAAM,EAAE,CAAC,IAAI,CAAA;QACxC,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,CAAC,MAAM,CAAC,EACN,SAAS,EAAE,YAAY,EACvB,iBAAiB,EAAE,SAAS,GACL;QACvB,MAAM,iBAAiB,GAAG,YAAY,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,CACjD,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CACnC,CAAA;QACD,KAAK,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC;YACtB,yDAAyD;YACzD,IAAI,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gBAChD,MAAM,EAAE,CAAA;gBACR,SAAQ;YACV,CAAC;YACD,mEAAmE;YACnE,oDAAoD;YACpD,IACE,YAAY;gBACV,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;iBACxB,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EACjC,CAAC;gBACD,MAAM,EAAE,CAAA;gBACR,SAAQ;YACV,CAAC;YACD,kEAAkE;YAClE,IACE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,iBAAiB,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAC3D,CAAC;gBACD,MAAM,EAAE,CAAA;YACV,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;;;;;;;;OAUG;IACH,KAAK,CAAC,GAAG,CACP,SAImB,EACnB,SAAS,GAAG,KAAK;QAEjB,MAAM,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,CAAA;QACpD,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,MAAM,KAAK,CAAC,sBAAsB,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;QAC1D,CAAC;QAED,OAAO,QAAQ,CAAe;YAC5B,KAAK,EAAE,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC;YACpB,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,CAAC;YAC1C,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,CACzC,MAAM,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC;YACzC,OAAO,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC;SAC5D,CAAC,CAAA;IACJ,CAAC;IAED;;;;;;;;;;OAUG;IACH,OAAO,CACL,SAIM,EACN,SAAS,GAAG,KAAK;QAEjB,MAAM,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,CAAA;QACpD,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,MAAM,KAAK,CAAC,sBAAsB,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;QAC1D,CAAC;QAED,OAAO,YAAY,CAAe;YAChC,KAAK,EAAE,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC;YACpB,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,CAAC;YAC1C,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,CACnC,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC;YACnC,OAAO,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC;SAC5D,CAAC,CAAA;IACJ,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,SAAS,CACd,WAAmB,EACnB,UAA2B,EAAE,IAAI,EAAE,EAAE,EAAE;QAEvC,IAAI,CAAC;YACH,IACE,CAAC,QAAQ,CACP,OAAO,CAAC,WAAW,EAAE,qBAAqB,CAAC,CAC5C,CAAC,MAAM,EAAE,EACV,CAAC;gBACD,OAAM;YACR,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,OAAM;QACR,CAAC;QACD,MAAM,EAAE,IAAI,GAAG,EAAE,EAAE,GAAG,OAAO,CAAA;QAC7B,OAAO,IAAI,QAAQ,CAAC,WAAW,EAAE,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;IACxD,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,IAAI,CACT,WAAmB,EACnB,UAA2B,EAAE,IAAI,EAAE,EAAE,EAAE;QAEvC,MAAM,EAAE,IAAI,GAAG,EAAE,EAAE,GAAG,OAAO,CAAA;QAC7B,OAAO,IAAI,QAAQ,CAAC,WAAW,EAAE,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;IACxD,CAAC;CACF;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,GAAG,EAAqB,CAAA;AAE1D;;GAEG;AACH,MAAM,OAAO,SAAS;IACpB,EAAE,CAAO;IACT,IAAI,CAAQ;IACZ,QAAQ,CAAQ;IAChB,QAAQ,CAAU;IAClB,MAAM,GAAa,EAAE,CAAA;IACrB,IAAI,CAAQ;IACZ,KAAK,CAAW;IAEhB,YAAY,IAAY,EAAE,QAAkB,EAAE,QAAgB;QAC5D,IAAI,CAAC,EAAE,GAAG,cAAc,CAAC,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,CAAA;QAC7C,cAAc,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;QAClC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;QACxB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;QACxB,IAAI,CAAC,IAAI,GAAG,QAAQ,CAAC,IAAI,IAAI,IAAI,CAAA;IACnC,CAAC;IAED,IAAI,IAAI;QACN,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,OAAO,IAAI,CAAC,KAAK,CAAA;QACnB,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA;QAClD,OAAO,IAAI,CAAC,KAAK,CAAA;IACnB,CAAC;CACF","sourcesContent":["import { DepID, joinDepIDTuple } from '@vltpkg/dep-id'\nimport { error } from '@vltpkg/error-cause'\nimport { PackageJson } from '@vltpkg/package-json'\nimport { Manifest } from '@vltpkg/types'\nimport { readFileSync, statSync } from 'fs'\nimport { GlobOptionsWithFileTypesFalse, globSync } from 'glob'\nimport { DepResults, graphRun, graphRunSync } from 'graph-run'\nimport { basename, resolve, posix } from 'path'\nimport { Path, PathScurry } from 'path-scurry'\nimport { parse } from 'polite-json'\nimport { minimatch } from 'minimatch'\n\nexport type WorkspacesLoadedConfig = {\n workspace?: string[]\n 'workspace-group'?: string[]\n}\n\n/**\n * The object passed to the constructor or {@link Monorepo#load} to limit which\n * {@link Workspace Workspaces} get loaded.\n */\nexport type LoadQuery = {\n /**\n * A glob pattern string, or an array of them. Only workspaces found\n * in paths matched will be loaded.\n */\n paths?: string[] | string\n /**\n * A string, or an array of strings. If set, only workspaces in the\n * specified groups named will be included, if set.\n */\n groups?: string[] | string\n}\n\n/**\n * Canonical form of the {@link WorkspaceConfig}, used\n * internally for consistency.\n */\nexport type WorkspaceConfigObject = Record<string, string[]>\n\n/**\n * Allowed datatype in the `vlt-workspaces.json` file.\n */\nexport type WorkspaceConfig =\n | string[]\n | WorkspaceConfigObject\n | string\n\n/**\n * Turn a {@link WorkspaceConfig} into a\n * {@link WorkspaceConfigObject}, or throw if it's not valid.\n */\nexport const asWSConfig = (\n conf: unknown,\n path?: string,\n): WorkspaceConfigObject => {\n assertWSConfig(conf, path)\n return (\n typeof conf === 'string' ? { packages: [conf] }\n : Array.isArray(conf) ? { packages: conf }\n : conf\n )\n}\n\n/**\n * Throw if the provided value is not a valid {@link WorkspaceConfig}\n */\nexport const assertWSConfig: (\n conf: unknown,\n path?: string,\n) => asserts conf is WorkspaceConfig = (\n conf: unknown,\n path?: string,\n) => {\n if (typeof conf === 'string') return conf\n\n if (Array.isArray(conf)) {\n for (const c of conf) {\n if (typeof c !== 'string') {\n throw error('Invalid workspace definition', {\n path,\n found: c,\n wanted: 'string',\n })\n }\n }\n return\n }\n\n if (conf && typeof conf === 'object') {\n for (const [group, value] of Object.entries(conf)) {\n if (typeof value === 'string') continue\n if (Array.isArray(value)) {\n for (const c of value) {\n if (typeof c !== 'string') {\n throw error('Invalid workspace definition', {\n path,\n name: group,\n found: c,\n wanted: 'string',\n })\n }\n }\n continue\n }\n throw error('Invalid workspace definition', {\n path,\n name: group,\n found: value,\n wanted: 'string | string[]',\n })\n }\n return\n }\n\n throw error('Invalid workspace definition', {\n path,\n found: conf,\n wanted:\n 'string | string[] | { [group: string]: string | string[] }',\n })\n}\n\nexport type MonorepoOptions = {\n /**\n * A {@link PackageJson} object, for sharing manifest caches\n */\n packageJson?: PackageJson\n /**\n * A {@link PathScurry} object, for use in globs\n */\n scurry?: PathScurry\n /** Parsed normalized contents of a `vlt-workspaces.json` file */\n config?: WorkspaceConfigObject\n /**\n * If set, then {@link Monorepo#load} will be called immediately with\n * this argument.\n */\n load?: LoadQuery\n}\n\n/**\n * Class representing a Monorepo containing multiple workspaces.\n *\n * Does not automatically look up the root, but that can be provided by\n * running `Config.load()`, since it stops seeking the route when a\n * `vlt-workspaces.json` file is encountered.\n */\nexport class Monorepo {\n /** The project root where vlt-workspaces.json is found */\n projectRoot: string\n /** Scurry object to cache all filesystem calls (mostly globs) */\n scurry: PathScurry\n\n // maps both name and path to the workspace objects\n #workspaces = new Map<string, Workspace>()\n #groups = new Map<string, Set<Workspace>>()\n #config?: WorkspaceConfigObject\n packageJson: PackageJson\n\n /**\n * Number of {@link Workspace} objects loaded in this Monorepo\n */\n get size(): number {\n return [...this.values()].length\n }\n\n constructor(projectRoot: string, options: MonorepoOptions = {}) {\n this.projectRoot = resolve(projectRoot)\n this.scurry = options.scurry ?? new PathScurry(projectRoot)\n this.packageJson = options.packageJson ?? new PackageJson()\n this.#config = options.config\n if (options.load) this.load(options.load)\n }\n\n /**\n * Load the workspace definitions from vlt-workspaces.json,\n * canonicalizing the result into the effective `{[group:string]:string[]}`\n * form.\n *\n * Eg:\n * - `\"src/*\"` => `{packages:[\"src/*\"]}`\n * - `{\"apps\": \"src/*\"}` => `{apps: [\"src/*\"]}`\n */\n get config(): WorkspaceConfigObject {\n if (this.#config) return this.#config\n const file = resolve(this.projectRoot, 'vlt-workspaces.json')\n let confData: string\n try {\n confData = readFileSync(file, 'utf8')\n } catch (er) {\n throw error('Not in a monorepo, no vlt-workspaces.json found', {\n path: this.projectRoot,\n cause: er as Error,\n })\n }\n let parsed: unknown\n try {\n parsed = parse(confData)\n } catch (er) {\n throw error('Invalid vlt-workspaces.json file', {\n path: this.projectRoot,\n cause: er as Error,\n })\n }\n this.#config = asWSConfig(parsed, file)\n return this.#config\n }\n\n /**\n * Iterating the Monorepo object yields the workspace objects, in as close to\n * topological dependency order as possible.\n */\n *[Symbol.iterator](): Generator<Workspace, void, void> {\n const [ws] = [...this.values()]\n if (!ws) return\n // leverage the fact that graphRun returns results in\n // as close to topological order as possible.\n for (const workspace of this.runSync(() => {}).keys()) {\n yield workspace\n }\n }\n\n /**\n * Iterating the Monorepo object yields the workspace objects, in as close to\n * topological dependency order as possible.\n */\n async *[Symbol.asyncIterator](): AsyncGenerator<\n Workspace,\n void,\n void\n > {\n const [ws] = [...this.values()]\n if (!ws) return\n for (const workspace of (await this.run(() => {})).keys()) {\n yield workspace\n }\n }\n\n /**\n * By default, loads all workspaces reachable in the Monorepo.\n *\n * If provided with one (`string`)or more (`string[]`) group names in\n * the {@link LoadQuery#groups} field, then only Workspaces in the named\n * group(s) will be considered. Note that group names are unique string\n * matches, not globs.\n *\n * If provided with a set of arbitrary path arguments, then only paths\n * patching the provided pattern(s) will be included.\n *\n * These two options intersect, so\n * `load({groups:'foo', paths:'./foo/[xy]*'})` will only load the workspaces\n * in the group `foo` that match the paths glob.\n */\n load(query: LoadQuery = {}): this {\n const paths = new Set(\n typeof query.paths === 'string' ?\n [query.paths]\n : (query.paths ?? []),\n )\n const groups = new Set(\n typeof query.groups === 'string' ?\n [query.groups]\n : (query.groups ?? []),\n )\n\n const groupsExpanded: Record<string, Set<string>> = {}\n for (const [group, pattern] of Object.entries(this.config)) {\n if (groups.size && !groups.has(group)) continue\n groupsExpanded[group] = this.#glob(pattern)\n }\n const filter = paths.size ? this.#glob([...paths]) : paths\n\n // if we specified paths, but none matched, nothing to do\n if (paths.size && !filter.size) return this\n\n for (const [group, matches] of Object.entries(groupsExpanded)) {\n for (const path of matches) {\n if (filter.size && !filter.has(path)) continue\n this.#loadWS(path, group)\n }\n }\n\n return this\n }\n\n // Either load a workspace from disk, or from our internal set,\n // and assign it to the named group\n #loadWS(path: string, group?: string): Workspace {\n const fullpath = resolve(this.projectRoot, path)\n const loaded = this.#workspaces.get(fullpath)\n if (loaded) return loaded\n const fromCache = workspaceCache.get(fullpath)\n const manifest =\n fromCache?.manifest ?? this.packageJson.read(fullpath)\n const ws = fromCache ?? new Workspace(path, manifest, fullpath)\n if (group) ws.groups.push(group)\n this.#workspaces.set(ws.fullpath, ws)\n this.#workspaces.set(ws.path, ws)\n this.#workspaces.set(ws.name, ws)\n for (const name of ws.groups) {\n const group = this.#groups.get(name) ?? new Set()\n group.add(ws)\n this.#groups.set(name, group)\n }\n return ws\n }\n\n // can't be cached, because it's dependent on the matches set\n // but still worthwhile to have it defined in one place\n #globOptions(matches: Set<string>): GlobOptionsWithFileTypesFalse {\n // if the entry or any of its parent dirs are already matched,\n // then we should not explore further down that directory tree.\n // if we hit the projectRoot then stop searching.\n const inMatches = (p?: Path): boolean => {\n return (\n !!p?.relativePosix() &&\n (matches.has(p.relativePosix()) || inMatches(p.parent))\n )\n }\n\n return {\n root: this.projectRoot,\n cwd: this.projectRoot,\n posix: true,\n scurry: this.scurry,\n withFileTypes: false,\n ignore: {\n childrenIgnored: p =>\n basename(p.relativePosix()) === 'node_modules' ||\n inMatches(p),\n // ignore if fails to load package.json\n ignored: p => {\n p.lstatSync()\n const rel = p.relativePosix()\n if (!rel) return true\n const maybeDelete: string[] = []\n for (const m of matches) {\n if (rel.startsWith(m + '/')) return true\n if (m.startsWith(rel + '/')) {\n maybeDelete.push(m)\n }\n }\n if (!p.isDirectory()) return true\n const pj = p.resolve('package.json').lstatSync()\n if (!pj?.isFile()) return true\n try {\n this.packageJson.read(p.fullpath())\n } catch {\n return true\n }\n for (const m of maybeDelete) {\n matches.delete(m)\n }\n matches.add(rel)\n return false\n },\n },\n }\n }\n\n #glob(pattern: string[] | string) {\n const matches = new Set<string>()\n globSync(pattern, this.#globOptions(matches))\n return matches\n }\n\n /**\n * Return the array of workspace dependencies that are found in\n * the loaded set, for use in calculating dependency graph order for\n * build operations.\n *\n * This does *not* get the full set of dependencies, or expand any\n * `workspace:` dependencies that are not loaded.\n *\n * Call with the `forceLoad` param set to `true` to attempt a full\n * load if any deps are not currently loaded.\n */\n getDeps(ws: Workspace, forceLoad = false): Workspace[] {\n // load manifest and find workspace: deps\n // filter by those loaded\n const { manifest } = ws\n const depWorkspaces: Workspace[] = []\n let didForceLoad = false\n for (const depType of [\n 'dependencies',\n 'devDependencies',\n 'optionalDependencies',\n 'peerDependencies',\n ] as const) {\n const deps = manifest[depType]\n if (!deps) continue\n for (const [dep, spec] of Object.entries(deps)) {\n if (spec.startsWith('workspace:')) {\n let depWS = this.#workspaces.get(dep)\n if (!depWS) {\n if (!forceLoad) continue\n if (didForceLoad) continue\n didForceLoad = true\n this.load()\n depWS = this.#workspaces.get(dep)\n if (!depWS) continue\n }\n depWorkspaces.push(depWS)\n }\n }\n }\n return depWorkspaces\n }\n\n onCycle(\n _ws: Workspace,\n _cycle: Workspace[],\n _depPath: Workspace[],\n ) {\n // XXX - process logging? Need to say something like:\n // Cyclical workspace dependency warning!\n // When evaluating dependency ${ws.name} via ${\n // path.map(ws => ws.name).join(' -> ')\n // }, a dependency cycle was detected: ${\n // cycle.map(ws => ws.name).join(' -> ')\n // }. Operation will continue, but dependency order not guaranteed.`\n }\n\n /**\n * Return the set of workspaces in the named group.\n * If the group is not one we know about, then undefined is returned.\n */\n group(group: string) {\n return this.#groups.get(group)\n }\n\n /**\n * Get a loaded workspace by path or name.\n *\n * Note that this can only return workspaces that were ingested via a\n * previous call to {@link Monorepo#load}.\n */\n get(nameOrPath: string) {\n return this.#workspaces.get(nameOrPath)\n }\n\n /**\n * get the list of all loaded workspace names used as keys\n */\n *names() {\n for (const [key, ws] of this.#workspaces) {\n if (key === ws.name) yield key\n }\n }\n\n /**\n * get the list of all loaded workspace paths used as keys\n */\n *paths() {\n for (const [key, ws] of this.#workspaces) {\n if (key === ws.path) yield key\n }\n }\n\n /**\n * get the workspace objects in no particular order.\n * this is ever so slightly faster than iterating, because it doesn't\n * explore the graph to yield results in topological dependency order,\n * and should be used instead when order doesn't matter.\n */\n *values() {\n const seen = new Set<string>()\n for (const ws of this.#workspaces.values()) {\n if (seen.has(ws.fullpath)) continue\n seen.add(ws.fullpath)\n yield ws\n }\n }\n\n /**\n * Get all the keys (package names and paths) for loaded workspaces.\n * Union of {@link Monorepo#names} and {@link Monorepo#paths}\n */\n *keys() {\n for (const ws of this.values()) {\n yield ws.path\n if (ws.name !== ws.path) yield ws.name\n }\n }\n\n /**\n * Filter the monorepo object yielding the workspace objects that matches\n * either of the {@link WorkspacesLoadedConfig} options provided, in as close\n * to topological dependency order as possible.\n */\n *filter({\n workspace: namesOrPaths,\n 'workspace-group': groupName,\n }: WorkspacesLoadedConfig) {\n const globPatternChecks = namesOrPaths?.map(glob =>\n minimatch.filter(posix.join(glob)),\n )\n for (const ws of this) {\n // check if any group has any of the provided group names\n if (groupName?.some(i => ws.groups.includes(i))) {\n yield ws\n continue\n }\n // check if any workspace-provided name directly matches any of the\n // configured workspaces by either name or file path\n if (\n namesOrPaths\n ?.map(i => posix.join(i))\n .some(i => ws.keys.includes(i))\n ) {\n yield ws\n continue\n }\n // check if one of the workspace values are matching glob patterns\n if (\n ws.keys.some(key => globPatternChecks?.some(fn => fn(key)))\n ) {\n yield ws\n }\n }\n }\n\n /**\n * Run an operation asynchronously over all loaded workspaces\n *\n * If the `forceLoad` param is true, then it will attempt to do a full load\n * when encountering a `workspace:` dependency that isn't loaded.\n *\n * Note that because the return type appears in the parameters of the\n * operation function, it must be set explicitly either in the operation\n * function signature or by calling `run<MyType>` or it'll fall back to\n * `unknown`, similar to `Array.reduce()`, and for the same reason.\n */\n async run<R>(\n operation: (\n s: Workspace,\n signal: AbortSignal,\n depResults: DepResults<Workspace, R>,\n ) => Promise<R> | R,\n forceLoad = false,\n ) {\n const [ws, ...rest] = [...this.#workspaces.values()]\n if (!ws) {\n throw error('No workspaces loaded', undefined, this.run)\n }\n\n return graphRun<Workspace, R>({\n graph: [ws, ...rest],\n getDeps: ws => this.getDeps(ws, forceLoad),\n visit: async (ws, signal, _, depResults) =>\n await operation(ws, signal, depResults),\n onCycle: (ws, cycle, path) => this.onCycle(ws, cycle, path),\n })\n }\n\n /**\n * Run an operation synchronously over all loaded workspaces\n *\n * If the `forceLoad` param is true, then it will attempt to do a full load\n * when encountering a `workspace:` dependency that isn't loaded.\n *\n * Note that because the return type appears in the parameters of the\n * operation function, it must be set explicitly either in the operation\n * function signature or by calling `runSync<MyType>` or it'll fall back to\n * `unknown`, similar to `Array.reduce()`, and for the same reason.\n */\n runSync<R>(\n operation: (\n s: Workspace,\n signal: AbortSignal,\n depResults: DepResults<Workspace, R>,\n ) => R,\n forceLoad = false,\n ) {\n const [ws, ...rest] = [...this.#workspaces.values()]\n if (!ws) {\n throw error('No workspaces loaded', undefined, this.run)\n }\n\n return graphRunSync<Workspace, R>({\n graph: [ws, ...rest],\n getDeps: ws => this.getDeps(ws, forceLoad),\n visit: (ws, signal, _, depResults) =>\n operation(ws, signal, depResults),\n onCycle: (ws, cycle, path) => this.onCycle(ws, cycle, path),\n })\n }\n\n /**\n * Convenience method to instantiate and load in one call.\n * Returns undefined if the project is not a monorepo workspaces\n * root, otherwise returns the loaded Monorepo.\n */\n static maybeLoad(\n projectRoot: string,\n options: MonorepoOptions = { load: {} },\n ) {\n try {\n if (\n !statSync(\n resolve(projectRoot, 'vlt-workspaces.json'),\n ).isFile()\n ) {\n return\n }\n } catch {\n return\n }\n const { load = {} } = options\n return new Monorepo(projectRoot, { ...options, load })\n }\n\n /**\n * Convenience method to instantiate and load in one call.\n * Throws if called on a directory that is not a workspaces root.\n */\n static load(\n projectRoot: string,\n options: MonorepoOptions = { load: {} },\n ) {\n const { load = {} } = options\n return new Monorepo(projectRoot, { ...options, load })\n }\n}\n\nexport const workspaceCache = new Map<string, Workspace>()\n\n/**\n * Class representing a single Workspace in a {@link Monorepo}\n */\nexport class Workspace {\n id: DepID\n path: string\n fullpath: string\n manifest: Manifest\n groups: string[] = []\n name: string\n #keys?: string[]\n\n constructor(path: string, manifest: Manifest, fullpath: string) {\n this.id = joinDepIDTuple(['workspace', path])\n workspaceCache.set(fullpath, this)\n this.path = path\n this.fullpath = fullpath\n this.manifest = manifest\n this.name = manifest.name ?? path\n }\n\n get keys(): string[] {\n if (this.#keys) {\n return this.#keys\n }\n this.#keys = [this.name, this.path, this.fullpath]\n return this.#keys\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vltpkg/workspaces",
|
|
3
|
+
"description": "Utility for working with vlt workspaces",
|
|
4
|
+
"version": "0.0.0-0.1730239248325",
|
|
5
|
+
"tshy": {
|
|
6
|
+
"selfLink": false,
|
|
7
|
+
"dialects": [
|
|
8
|
+
"esm"
|
|
9
|
+
],
|
|
10
|
+
"exports": {
|
|
11
|
+
"./package.json": "./package.json",
|
|
12
|
+
".": "./src/index.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"glob": "^11.0.0",
|
|
17
|
+
"graph-run": "^1.0.4",
|
|
18
|
+
"minimatch": "^10.0.1",
|
|
19
|
+
"path-scurry": "^2.0.0",
|
|
20
|
+
"polite-json": "^5.0.0",
|
|
21
|
+
"walk-up-path": "^4.0.0",
|
|
22
|
+
"@vltpkg/dep-id": "0.0.0-0.1730239248325",
|
|
23
|
+
"@vltpkg/types": "0.0.0-0.1730239248325",
|
|
24
|
+
"@vltpkg/package-json": "0.0.0-0.1730239248325",
|
|
25
|
+
"@vltpkg/error-cause": "0.0.0-0.1730239248325"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@eslint/js": "^9.8.0",
|
|
29
|
+
"@types/eslint__js": "^8.42.3",
|
|
30
|
+
"@types/node": "^22.4.1",
|
|
31
|
+
"eslint": "^9.8.0",
|
|
32
|
+
"prettier": "^3.3.2",
|
|
33
|
+
"tap": "^21.0.1",
|
|
34
|
+
"tshy": "^3.0.2",
|
|
35
|
+
"typescript": "^5.5.4",
|
|
36
|
+
"typescript-eslint": "^8.0.1"
|
|
37
|
+
},
|
|
38
|
+
"license": "BSD-2-Clause-Patent",
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": "20 || >=22"
|
|
41
|
+
},
|
|
42
|
+
"tap": {
|
|
43
|
+
"extends": "../../tap-config.yaml"
|
|
44
|
+
},
|
|
45
|
+
"prettier": "../../.prettierrc.js",
|
|
46
|
+
"module": "./dist/esm/index.js",
|
|
47
|
+
"type": "module",
|
|
48
|
+
"exports": {
|
|
49
|
+
"./package.json": "./package.json",
|
|
50
|
+
".": {
|
|
51
|
+
"import": {
|
|
52
|
+
"types": "./dist/esm/index.d.ts",
|
|
53
|
+
"default": "./dist/esm/index.js"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"files": [
|
|
58
|
+
"dist"
|
|
59
|
+
],
|
|
60
|
+
"scripts": {
|
|
61
|
+
"format": "prettier --write . --log-level warn --ignore-path ../../.prettierignore --cache",
|
|
62
|
+
"format:check": "prettier --check . --ignore-path ../../.prettierignore --cache",
|
|
63
|
+
"lint": "eslint . --fix",
|
|
64
|
+
"lint:check": "eslint .",
|
|
65
|
+
"presnap": "tshy",
|
|
66
|
+
"snap": "tap",
|
|
67
|
+
"pretest": "tshy",
|
|
68
|
+
"test": "tap"
|
|
69
|
+
}
|
|
70
|
+
}
|