rman 1.3.0 → 2.0.0-beta.3
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/README.md +65 -31
- package/builtins/platforms/node/augmentation/rmanrc.augmentation.d.ts +83 -0
- package/builtins/platforms/node/augmentation/system-info.augmentation.d.ts +26 -0
- package/builtins/platforms/node/augmentation/system-info.augmentation.js +79 -0
- package/builtins/platforms/node/commands/ci.command.d.ts +133 -0
- package/builtins/platforms/node/commands/ci.command.js +60 -0
- package/builtins/platforms/node/commands/clean.command.d.ts +185 -0
- package/builtins/platforms/node/commands/clean.command.js +74 -0
- package/builtins/platforms/node/index.d.ts +8 -0
- package/builtins/platforms/node/index.js +8 -0
- package/builtins/platforms/node/node-config.interface.d.ts +76 -0
- package/builtins/platforms/node/node-config.interface.js +7 -0
- package/builtins/platforms/node/node-manifest.provider.d.ts +68 -0
- package/builtins/platforms/node/node-manifest.provider.js +125 -0
- package/builtins/platforms/node/node.platform.d.ts +53 -0
- package/builtins/platforms/node/node.platform.js +134 -0
- package/builtins/platforms/node/services/ci.service.d.ts +47 -0
- package/builtins/platforms/node/services/ci.service.js +213 -0
- package/builtins/platforms/node/services/clean.service.d.ts +53 -0
- package/builtins/platforms/node/services/clean.service.js +237 -0
- package/builtins/platforms/node/services/publish.service.d.ts +114 -0
- package/builtins/platforms/node/services/publish.service.js +371 -0
- package/builtins/platforms/node/services/version-plan.service.d.ts +44 -0
- package/builtins/platforms/node/services/version-plan.service.js +58 -0
- package/builtins/platforms/node/utils/workspace-range.d.ts +26 -0
- package/builtins/platforms/node/utils/workspace-range.js +28 -0
- package/builtins/presets/index.d.ts +15 -0
- package/builtins/presets/index.js +57 -0
- package/builtins/presets/node.d.ts +16 -0
- package/builtins/presets/node.js +57 -0
- package/{services → builtins/publish-targets/docker}/docker-publish.service.d.ts +52 -31
- package/{services → builtins/publish-targets/docker}/docker-publish.service.js +49 -26
- package/builtins/publish-targets/docker/docker.target.d.ts +54 -0
- package/builtins/publish-targets/docker/docker.target.js +40 -0
- package/builtins/publish-targets/docker/index.d.ts +2 -0
- package/builtins/publish-targets/docker/index.js +2 -0
- package/builtins/publish-targets/npm/index.d.ts +2 -0
- package/builtins/publish-targets/npm/index.js +2 -0
- package/builtins/publish-targets/npm/npm-publish-target.d.ts +73 -0
- package/builtins/publish-targets/npm/npm-publish-target.js +95 -0
- package/builtins/publish-targets/npm/npm-view.d.ts +48 -0
- package/builtins/publish-targets/npm/npm-view.js +71 -0
- package/cli.d.ts +8 -0
- package/cli.js +228 -93
- package/commands/build.command.d.ts +177 -3
- package/commands/build.command.js +20 -10
- package/commands/changelog.command.d.ts +296 -3
- package/commands/changelog.command.js +127 -43
- package/commands/config.command.d.ts +44 -3
- package/commands/config.command.js +53 -24
- package/commands/diff.command.d.ts +37 -3
- package/commands/diff.command.js +25 -16
- package/commands/exec.command.d.ts +193 -3
- package/commands/exec.command.js +60 -58
- package/commands/github-release.command.d.ts +154 -3
- package/commands/github-release.command.js +67 -37
- package/commands/import.command.d.ts +36 -3
- package/commands/import.command.js +28 -20
- package/commands/info.command.d.ts +35 -7
- package/commands/info.command.js +36 -30
- package/commands/lint.command.d.ts +176 -0
- package/commands/lint.command.js +34 -0
- package/commands/list.command.d.ts +163 -3
- package/commands/list.command.js +109 -71
- package/commands/publish.command.d.ts +231 -0
- package/commands/publish.command.js +304 -0
- package/commands/run.command.d.ts +186 -6
- package/commands/run.command.js +26 -72
- package/commands/test.command.d.ts +173 -3
- package/commands/test.command.js +16 -10
- package/commands/version.command.d.ts +330 -3
- package/commands/version.command.js +292 -78
- package/commands.d.ts +32 -0
- package/commands.js +28 -0
- package/constants.js +1 -1
- package/core/application.d.ts +116 -0
- package/core/application.js +160 -0
- package/core/classes/package.d.ts +219 -0
- package/core/classes/package.js +214 -0
- package/core/classes/registry.d.ts +30 -0
- package/core/classes/registry.js +47 -0
- package/core/{repository.d.ts → classes/repository.d.ts} +69 -20
- package/core/{repository.js → classes/repository.js} +195 -75
- package/core/classes/service.d.ts +49 -0
- package/core/classes/service.js +40 -0
- package/core/{version-scheme.d.ts → classes/version-scheme.d.ts} +23 -1
- package/core/{version-scheme.js → classes/version-scheme.js} +29 -1
- package/core/classes/workspace.d.ts +275 -0
- package/core/classes/workspace.js +537 -0
- package/core/command-builder.d.ts +14 -0
- package/core/command-builder.js +94 -0
- package/core/config/config-file-scope.d.ts +107 -0
- package/core/config/config-file-scope.js +226 -0
- package/core/config/config-interpolator.d.ts +164 -0
- package/core/config/config-interpolator.js +577 -0
- package/core/config/config-paths.d.ts +65 -0
- package/core/config/config-paths.js +78 -0
- package/core/config/config-reader.d.ts +192 -0
- package/core/config/config-reader.js +727 -0
- package/core/{merge-config.d.ts → config/merge-config.d.ts} +33 -34
- package/core/config/merge-config.js +260 -0
- package/core/{resolve-target.d.ts → config/resolve-config-target.d.ts} +1 -1
- package/core/{resolve-target.js → config/resolve-config-target.js} +1 -1
- package/core/{custom-command.d.ts → interfaces/custom-command.d.ts} +46 -10
- package/core/{custom-command.js → interfaces/custom-command.js} +44 -17
- package/core/{manifest.d.ts → interfaces/manifest.d.ts} +49 -27
- package/core/interfaces/manifest.js +132 -0
- package/core/interfaces/plugin.d.ts +174 -0
- package/core/interfaces/plugin.js +124 -0
- package/core/interfaces/publish-target.d.ts +136 -0
- package/core/interfaces/publish-target.js +43 -0
- package/core/{run-step.d.ts → interfaces/run-step.d.ts} +4 -4
- package/core/interfaces/run-step.js +1 -0
- package/core/plugin-loader.d.ts +25 -0
- package/core/plugin-loader.js +53 -0
- package/index.d.ts +122 -24
- package/index.js +66 -16
- package/interfaces/config-scope.interface.d.ts +223 -0
- package/interfaces/config-scope.interface.js +1 -0
- package/interfaces/rman-config.interface.d.ts +821 -203
- package/interfaces/rman-config.interface.js +78 -1
- package/package.json +3 -1
- package/services/change-hash.service.d.ts +42 -8
- package/services/change-hash.service.js +92 -13
- package/services/changelog.service.d.ts +91 -52
- package/services/changelog.service.js +460 -74
- package/services/config-service.d.ts +8 -0
- package/services/config-service.js +3 -0
- package/services/exec.service.d.ts +23 -12
- package/services/exec.service.js +14 -9
- package/services/github-release.service.d.ts +45 -34
- package/services/github-release.service.js +15 -12
- package/services/import.service.d.ts +25 -14
- package/services/import.service.js +9 -5
- package/services/list.service.d.ts +67 -13
- package/services/list.service.js +62 -15
- package/services/run.service.d.ts +35 -22
- package/services/run.service.js +316 -233
- package/services/system-info.d.ts +1 -1
- package/services/version-plan.service.d.ts +35 -8
- package/services/version-plan.service.js +96 -36
- package/services/version.service.d.ts +34 -14
- package/services/version.service.js +35 -16
- package/services.d.ts +1 -1
- package/services.js +1 -1
- package/utils/bin-path.d.ts +6 -7
- package/utils/bin-path.js +7 -18
- package/utils/branch-guard.d.ts +30 -1
- package/utils/branch-guard.js +31 -0
- package/utils/color-yaml.d.ts +8 -0
- package/utils/color-yaml.js +86 -0
- package/utils/exec.d.ts +10 -0
- package/utils/exec.js +1 -1
- package/utils/git.d.ts +26 -0
- package/utils/git.js +65 -0
- package/utils/logger.d.ts +2 -2
- package/utils/logger.js +1 -1
- package/utils/package-filter.d.ts +135 -8
- package/utils/package-filter.js +204 -16
- package/utils/printable-config.d.ts +9 -1
- package/utils/printable-config.js +46 -1
- package/utils/progress-panel.d.ts +9 -0
- package/utils/progress-panel.js +23 -1
- package/utils/release-version.d.ts +1 -1
- package/utils/run-bin.d.ts +10 -0
- package/utils/run-bin.js +1 -1
- package/utils/run-options.d.ts +97 -0
- package/utils/run-options.js +81 -0
- package/utils/version-group.d.ts +31 -0
- package/utils/version-group.js +43 -0
- package/utils/version-stamp.d.ts +1 -1
- package/utils/version-stamp.js +1 -1
- package/commands/changed.command.d.ts +0 -3
- package/commands/changed.command.js +0 -29
- package/core/config.d.ts +0 -409
- package/core/config.js +0 -939
- package/core/extends-config.d.ts +0 -27
- package/core/extends-config.js +0 -89
- package/core/manifest.js +0 -150
- package/core/merge-config.js +0 -219
- package/core/package.d.ts +0 -83
- package/core/package.js +0 -106
- package/core/plugin.d.ts +0 -112
- package/core/plugin.js +0 -189
- package/core/workspace.d.ts +0 -68
- package/core/workspace.js +0 -83
- /package/{core/run-step.js → builtins/platforms/node/augmentation/rmanrc.augmentation.js} +0 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An ordered collection of contributions, held **on the application** rather than in a module.
|
|
3
|
+
*
|
|
4
|
+
* Every registry rman had was a module-level array (`const providers: Provider[] = []` in
|
|
5
|
+
* `manifest.ts`, `workspace.ts`, `bin-path.ts`, `run.service.ts`), which made it process-global:
|
|
6
|
+
* two repositories in one process shared it. The test suite needed a root hook emptying five of
|
|
7
|
+
* them before every single test, and without it whichever spec ran first decided the answer for
|
|
8
|
+
* the rest - so the core appeared to work in tests that had registered nothing.
|
|
9
|
+
*
|
|
10
|
+
* An instance per `RmanApplication` closes the whole class: a new application starts empty, and
|
|
11
|
+
* nothing has to be cleaned up afterwards - the root hook is deleted.
|
|
12
|
+
*
|
|
13
|
+
* **Registry, not service.** The distinction is multiplicity: a registry is for a question whose
|
|
14
|
+
* answer is the *sum* of what was contributed (every provider's bin directories, the first provider
|
|
15
|
+
* that recognizes a directory). A question with exactly one answer is a service, replaceable
|
|
16
|
+
* through `RmanApplication.getService`.
|
|
17
|
+
*/
|
|
18
|
+
export class Registry {
|
|
19
|
+
items = [];
|
|
20
|
+
/** Contributions in registration order, which for a plugin is `plugins` declaration order. */
|
|
21
|
+
get all() {
|
|
22
|
+
return this.items;
|
|
23
|
+
}
|
|
24
|
+
get size() {
|
|
25
|
+
return this.items.length;
|
|
26
|
+
}
|
|
27
|
+
/** Idempotent by identity: a plugin may both declare a contribution and register it itself, and
|
|
28
|
+
* the same function arriving twice would make the registry lie about what is in it. */
|
|
29
|
+
add(item) {
|
|
30
|
+
if (!this.items.includes(item))
|
|
31
|
+
this.items.push(item);
|
|
32
|
+
return this;
|
|
33
|
+
}
|
|
34
|
+
/** The first contribution `ask` gives an answer for - "first that recognizes it" resolution,
|
|
35
|
+
* which is how a directory finds the manifest reader and the workspace layout that claim it. */
|
|
36
|
+
first(ask) {
|
|
37
|
+
for (const item of this.items) {
|
|
38
|
+
const answer = ask(item);
|
|
39
|
+
if (answer !== undefined)
|
|
40
|
+
return answer;
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
[Symbol.iterator]() {
|
|
45
|
+
return this.items[Symbol.iterator]();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { type ConfigScope, type GitScope, type PackageScope, type RepositoryScope } from '
|
|
2
|
-
import
|
|
1
|
+
import { type ConfigScope, type GitScope, type PackageScope, type RepositoryScope } from '../../interfaces/config-scope.interface.js';
|
|
2
|
+
import { RmanApplication } from '../application.js';
|
|
3
|
+
import type { Platform } from '../interfaces/plugin.js';
|
|
3
4
|
import { Package } from './package.js';
|
|
5
|
+
import { Workspace } from './workspace.js';
|
|
4
6
|
export declare class Repository extends Package {
|
|
5
7
|
readonly dirname: string;
|
|
6
8
|
readonly monorepo: boolean;
|
|
@@ -12,7 +14,6 @@ export declare class Repository extends Package {
|
|
|
12
14
|
readonly rootPackage: Package;
|
|
13
15
|
/** Commands the repository's plugins contributed, loaded during `create` because the workspace
|
|
14
16
|
* providers they bring are needed before any package can be found. `cli.ts` registers them. */
|
|
15
|
-
pluginCommands: LoadedCommand[];
|
|
16
17
|
/**
|
|
17
18
|
* Cached repository scope - see `_repositoryScope`.
|
|
18
19
|
*
|
|
@@ -28,19 +29,33 @@ export declare class Repository extends Package {
|
|
|
28
29
|
* and because reading it is a subprocess: a deep walk of a package must not spawn one. */
|
|
29
30
|
private _git?;
|
|
30
31
|
/**
|
|
31
|
-
*
|
|
32
|
-
* of the bytes - see `readStructuredFile`.
|
|
32
|
+
* What `${{ file... }}` and `${{ read(...) }}` answer from, for every package.
|
|
33
33
|
*
|
|
34
|
-
* **
|
|
35
|
-
* is built once per package, so a cache living there would re-read a
|
|
36
|
-
* for every package that mentions it.
|
|
34
|
+
* **One per repository rather than one per scope, and that is the whole point of it**:
|
|
35
|
+
* `configScope` is built once per package, so a parse cache living there would re-read a
|
|
36
|
+
* repository-level file once for every package that mentions it. The cache inside is keyed by the
|
|
37
|
+
* identity of the bytes (`mtimeNs:size`), because rman writes JSON while it runs - `version`
|
|
38
|
+
* rewrites every bumped manifest and then re-interpolates its deferred hooks.
|
|
37
39
|
*/
|
|
38
|
-
private readonly
|
|
39
|
-
|
|
40
|
+
private readonly _files;
|
|
41
|
+
/**
|
|
42
|
+
* The application this repository belongs to - its services, its technologies, its logger.
|
|
43
|
+
*
|
|
44
|
+
* **Non-enumerable**, like `_repoScope` and `_git` beside it: the two things that walk a
|
|
45
|
+
* repository are `{...pkg}` spreads and the config scope, and an enumerable back-reference to the
|
|
46
|
+
* whole application would be dragged into both. A `Package` deliberately has no such field at
|
|
47
|
+
* all; a repository is never spread or serialized, which is what makes this one safe - measured,
|
|
48
|
+
* rather than assumed.
|
|
49
|
+
*/
|
|
50
|
+
readonly app: RmanApplication;
|
|
51
|
+
protected constructor(app: RmanApplication, dirname: string, monorepo: boolean, packages: Package[],
|
|
40
52
|
/** The directory `Repository.create()` was actually invoked from - unlike `dirname` (the
|
|
41
53
|
* resolved repository root, possibly several levels up), this is where the user's shell
|
|
42
54
|
* really was. Used by `currentPackage` to scope commands to "the package I'm standing in". */
|
|
43
|
-
cwd?: string
|
|
55
|
+
cwd?: string,
|
|
56
|
+
/** The root directory's own technology, from the same walk that found the packages - so the
|
|
57
|
+
* repository and its `rootPackage` agree without either searching the registry again. */
|
|
58
|
+
platform?: Platform);
|
|
44
59
|
/**
|
|
45
60
|
* The package whose own directory contains `cwd` (the deepest match, so a package nested
|
|
46
61
|
* inside another's directory resolves to the innermost one) - or `undefined` when `cwd` *is*
|
|
@@ -60,9 +75,21 @@ export declare class Repository extends Package {
|
|
|
60
75
|
* dirty, the reference point decides the rest: without `hash`, `committed`
|
|
61
76
|
* means committed but not yet in the upstream branch; with `hash`, `changed`
|
|
62
77
|
* means it differs from that commit. Otherwise a package is `clean`.
|
|
78
|
+
*
|
|
79
|
+
* **Keyed by `Package.selector`**, which is what addresses a package - it was `name`, and the two
|
|
80
|
+
* coincide wherever a technology names its packages. A repository whose does not had every such
|
|
81
|
+
* package answering to `""`, so one entry stood for all of them.
|
|
82
|
+
*
|
|
83
|
+
* **`includeRoot` is opt-in, and the reason is that the root's answer means something different.**
|
|
84
|
+
* Its directory contains every other package, so the same rule - "does a changed file fall under
|
|
85
|
+
* this directory" - reports `dirty` for the root whenever *anything* in the repository is dirty.
|
|
86
|
+
* That is the honest reading of the rule rather than a bug, and it is not what `run --changed`
|
|
87
|
+
* wants, so only a caller that asked for the root gets it (`rman list`'s table, which shows the
|
|
88
|
+
* root as the tree's own row).
|
|
63
89
|
*/
|
|
64
90
|
listStatus(options?: {
|
|
65
91
|
hash?: string;
|
|
92
|
+
includeRoot?: boolean;
|
|
66
93
|
}): Promise<Record<string, Repository.PackageStatus>>;
|
|
67
94
|
/**
|
|
68
95
|
* The scope a `${{ ... }}` expression is evaluated against for `pkg`, optionally with the version
|
|
@@ -89,13 +116,29 @@ export declare class Repository extends Package {
|
|
|
89
116
|
* nothing under `getPackages()` is the root, so only its own unmarked config applies.
|
|
90
117
|
*/
|
|
91
118
|
/**
|
|
92
|
-
* Gives every package its `repository
|
|
93
|
-
*
|
|
119
|
+
* Gives every package its `repository`, and hangs the `parent`/`children` tree off the walk that
|
|
120
|
+
* found them - before any config is resolved, since a config expression or a provider may already
|
|
121
|
+
* want to navigate from a package outwards.
|
|
122
|
+
*
|
|
123
|
+
* **The containment is read from the tree rather than recomputed from paths.** It used to be an
|
|
124
|
+
* O(n²) sweep comparing every package's directory against every other's and keeping the longest
|
|
125
|
+
* prefix - which is the same question `Workspace.walk` answers on the way down, asked again
|
|
126
|
+
* afterwards with the answer thrown away. Two places deriving one relationship is two places to
|
|
127
|
+
* disagree; there is one now.
|
|
128
|
+
*
|
|
129
|
+
* **`parent` is defined non-enumerably, `children` is a plain field**, which is the one asymmetry
|
|
130
|
+
* here and it is deliberate: a tree is serialized downwards, so `children` has to be walkable and
|
|
131
|
+
* `parent` must not be, or every `JSON.stringify` is a cycle. The same way `Repository.app` and
|
|
132
|
+
* `ORIGINS` travel.
|
|
94
133
|
*
|
|
95
134
|
* A repository's own `repository` is itself, which reads oddly and is the honest answer:
|
|
96
135
|
* `Repository extends Package`, so the repository *is* a package of its own repository.
|
|
97
136
|
*/
|
|
98
|
-
protected _linkPackages(): void;
|
|
137
|
+
protected _linkPackages(tree: Workspace.Node, nodes: Workspace.Node[], packages: Package[]): void;
|
|
138
|
+
/**
|
|
139
|
+
* Bakes every package's config: the raw one the workspace cascaded, with every `${{ }}` and value
|
|
140
|
+
* function resolved against that package's scope.
|
|
141
|
+
*/
|
|
99
142
|
protected _resolveConfigs(): Promise<void>;
|
|
100
143
|
protected _topoSortPackages(packages: Package[]): void;
|
|
101
144
|
protected _packageScope(pkg: Package, targetVersion?: string): PackageScope;
|
|
@@ -131,20 +174,26 @@ export declare class Repository extends Package {
|
|
|
131
174
|
* otherwise: the plugins that know what a package is are named in the config file this step
|
|
132
175
|
* is looking for.
|
|
133
176
|
* 2. **Load the plugins** the root's config names, which registers their workspace providers
|
|
134
|
-
* (
|
|
177
|
+
* (their commands arrive through `.rmanrc "commands"`, which `cli.ts` reads).
|
|
135
178
|
* 3. **Ask the providers** for the layout. None recognizing it means a repository that is itself
|
|
136
179
|
* the one package.
|
|
137
180
|
*
|
|
138
|
-
* **A repository
|
|
181
|
+
* **A repository no technology recognizes has no packages beyond itself**, and that is the
|
|
139
182
|
* boundary working rather than failing: `workspaces` in a `package.json` is npm's idea, so it
|
|
140
|
-
* takes
|
|
183
|
+
* takes a platform that reads one for the directory to be a workspace at all. rman's own presets
|
|
184
|
+
* are laid under every root (see `DEFAULT_PRESETS`), so a plain Node repository needs no config
|
|
185
|
+
* file to say so; anything else arrives through `extends` or `platforms`.
|
|
141
186
|
*/
|
|
142
187
|
static create(root?: string, options?: {
|
|
143
188
|
deep?: number;
|
|
189
|
+
app?: RmanApplication;
|
|
190
|
+
presets?: readonly string[];
|
|
144
191
|
}): Promise<Repository>;
|
|
145
|
-
/**
|
|
146
|
-
*
|
|
147
|
-
*
|
|
192
|
+
/**
|
|
193
|
+
* Takes the workspace's packages as this repository's own - the back-references a `Package` needs
|
|
194
|
+
* and the parent/child edges the walk established.
|
|
195
|
+
*/
|
|
196
|
+
protected _adopt(workspace: Workspace): void;
|
|
148
197
|
private static _init;
|
|
149
198
|
}
|
|
150
199
|
export declare namespace Repository {
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import semver from 'semver';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import {} from '../../interfaces/config-scope.interface.js';
|
|
5
|
+
import { GitHelper } from '../../utils/git.js';
|
|
6
|
+
import { RmanApplication } from '../application.js';
|
|
7
|
+
import { ConfigFileScope } from '../config/config-file-scope.js';
|
|
8
|
+
import { ConfigInterpolator } from '../config/config-interpolator.js';
|
|
9
|
+
import { DEFERRED_PATHS } from '../config/config-paths.js';
|
|
10
|
+
import { Manifest } from '../interfaces/manifest.js';
|
|
7
11
|
import { Package } from './package.js';
|
|
8
|
-
import { loadPlugins } from './plugin.js';
|
|
9
12
|
import { Workspace } from './workspace.js';
|
|
10
13
|
export class Repository extends Package {
|
|
11
14
|
dirname;
|
|
@@ -15,7 +18,6 @@ export class Repository extends Package {
|
|
|
15
18
|
rootPackage;
|
|
16
19
|
/** Commands the repository's plugins contributed, loaded during `create` because the workspace
|
|
17
20
|
* providers they bring are needed before any package can be found. `cli.ts` registers them. */
|
|
18
|
-
pluginCommands = [];
|
|
19
21
|
/**
|
|
20
22
|
* Cached repository scope - see `_repositoryScope`.
|
|
21
23
|
*
|
|
@@ -31,25 +33,40 @@ export class Repository extends Package {
|
|
|
31
33
|
* and because reading it is a subprocess: a deep walk of a package must not spawn one. */
|
|
32
34
|
_git;
|
|
33
35
|
/**
|
|
34
|
-
*
|
|
35
|
-
* of the bytes - see `readStructuredFile`.
|
|
36
|
+
* What `${{ file... }}` and `${{ read(...) }}` answer from, for every package.
|
|
36
37
|
*
|
|
37
|
-
* **
|
|
38
|
-
* is built once per package, so a cache living there would re-read a
|
|
39
|
-
* for every package that mentions it.
|
|
38
|
+
* **One per repository rather than one per scope, and that is the whole point of it**:
|
|
39
|
+
* `configScope` is built once per package, so a parse cache living there would re-read a
|
|
40
|
+
* repository-level file once for every package that mentions it. The cache inside is keyed by the
|
|
41
|
+
* identity of the bytes (`mtimeNs:size`), because rman writes JSON while it runs - `version`
|
|
42
|
+
* rewrites every bumped manifest and then re-interpolates its deferred hooks.
|
|
40
43
|
*/
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
_files = new ConfigFileScope();
|
|
45
|
+
/**
|
|
46
|
+
* The application this repository belongs to - its services, its technologies, its logger.
|
|
47
|
+
*
|
|
48
|
+
* **Non-enumerable**, like `_repoScope` and `_git` beside it: the two things that walk a
|
|
49
|
+
* repository are `{...pkg}` spreads and the config scope, and an enumerable back-reference to the
|
|
50
|
+
* whole application would be dragged into both. A `Package` deliberately has no such field at
|
|
51
|
+
* all; a repository is never spread or serialized, which is what makes this one safe - measured,
|
|
52
|
+
* rather than assumed.
|
|
53
|
+
*/
|
|
54
|
+
app;
|
|
55
|
+
constructor(app, dirname, monorepo, packages,
|
|
43
56
|
/** The directory `Repository.create()` was actually invoked from - unlike `dirname` (the
|
|
44
57
|
* resolved repository root, possibly several levels up), this is where the user's shell
|
|
45
58
|
* really was. Used by `currentPackage` to scope commands to "the package I'm standing in". */
|
|
46
|
-
cwd = dirname
|
|
47
|
-
|
|
59
|
+
cwd = dirname,
|
|
60
|
+
/** The root directory's own technology, from the same walk that found the packages - so the
|
|
61
|
+
* repository and its `rootPackage` agree without either searching the registry again. */
|
|
62
|
+
platform) {
|
|
63
|
+
super(dirname, app, platform);
|
|
48
64
|
this.dirname = dirname;
|
|
49
65
|
this.monorepo = monorepo;
|
|
50
66
|
this.packages = packages;
|
|
51
67
|
this.cwd = cwd;
|
|
52
|
-
this
|
|
68
|
+
Object.defineProperty(this, 'app', { value: app, enumerable: false, writable: false });
|
|
69
|
+
this.rootPackage = new Package(dirname, app, platform);
|
|
53
70
|
if (!monorepo)
|
|
54
71
|
this.packages = [this.rootPackage];
|
|
55
72
|
// Config resolution can load a `.rmanrc.cjs`/`.mjs`/`.js` module (dynamic `import()`, always
|
|
@@ -94,11 +111,22 @@ export class Repository extends Package {
|
|
|
94
111
|
* dirty, the reference point decides the rest: without `hash`, `committed`
|
|
95
112
|
* means committed but not yet in the upstream branch; with `hash`, `changed`
|
|
96
113
|
* means it differs from that commit. Otherwise a package is `clean`.
|
|
114
|
+
*
|
|
115
|
+
* **Keyed by `Package.selector`**, which is what addresses a package - it was `name`, and the two
|
|
116
|
+
* coincide wherever a technology names its packages. A repository whose does not had every such
|
|
117
|
+
* package answering to `""`, so one entry stood for all of them.
|
|
118
|
+
*
|
|
119
|
+
* **`includeRoot` is opt-in, and the reason is that the root's answer means something different.**
|
|
120
|
+
* Its directory contains every other package, so the same rule - "does a changed file fall under
|
|
121
|
+
* this directory" - reports `dirty` for the root whenever *anything* in the repository is dirty.
|
|
122
|
+
* That is the honest reading of the rule rather than a bug, and it is not what `run --changed`
|
|
123
|
+
* wants, so only a caller that asked for the root gets it (`rman list`'s table, which shows the
|
|
124
|
+
* root as the tree's own row).
|
|
97
125
|
*/
|
|
98
126
|
async listStatus(options) {
|
|
99
127
|
const hash = options?.hash;
|
|
100
128
|
const git = new GitHelper({ cwd: this.dirname });
|
|
101
|
-
const packages = this.getPackages();
|
|
129
|
+
const packages = options?.includeRoot ? [this.rootPackage, ...this.getPackages()] : this.getPackages();
|
|
102
130
|
const belongsTo = (p, files) => files.some(f => !path.relative(p.dirname, f).startsWith('..'));
|
|
103
131
|
const [dirtyFiles, referenceFiles] = await Promise.all([
|
|
104
132
|
git.listDirtyFiles({ absolute: true }),
|
|
@@ -107,11 +135,11 @@ export class Repository extends Package {
|
|
|
107
135
|
const result = {};
|
|
108
136
|
for (const p of packages) {
|
|
109
137
|
if (belongsTo(p, dirtyFiles))
|
|
110
|
-
result[p.
|
|
138
|
+
result[p.selector] = 'dirty';
|
|
111
139
|
else if (belongsTo(p, referenceFiles))
|
|
112
|
-
result[p.
|
|
140
|
+
result[p.selector] = hash ? 'changed' : 'committed';
|
|
113
141
|
else
|
|
114
|
-
result[p.
|
|
142
|
+
result[p.selector] = 'clean';
|
|
115
143
|
}
|
|
116
144
|
return result;
|
|
117
145
|
}
|
|
@@ -131,10 +159,10 @@ export class Repository extends Package {
|
|
|
131
159
|
repository: this._repositoryScope(),
|
|
132
160
|
/** `pkg.dirname`, not the repository root: a `"[*]"` block asking whether
|
|
133
161
|
* `tsconfig-build.json` exists has to be answered per package. */
|
|
134
|
-
file:
|
|
162
|
+
file: this._files.fileScope(pkg.dirname),
|
|
135
163
|
/** Same base directory as `file`, so one `"[*]"` declaration reads each package's own copy -
|
|
136
164
|
* and the cache is the repository's, so a file they *share* is parsed once. */
|
|
137
|
-
read:
|
|
165
|
+
read: this._files.readScope(pkg.dirname),
|
|
138
166
|
env: { ...process.env },
|
|
139
167
|
semver,
|
|
140
168
|
path,
|
|
@@ -173,52 +201,71 @@ export class Repository extends Package {
|
|
|
173
201
|
* nothing under `getPackages()` is the root, so only its own unmarked config applies.
|
|
174
202
|
*/
|
|
175
203
|
/**
|
|
176
|
-
* Gives every package its `repository
|
|
177
|
-
*
|
|
204
|
+
* Gives every package its `repository`, and hangs the `parent`/`children` tree off the walk that
|
|
205
|
+
* found them - before any config is resolved, since a config expression or a provider may already
|
|
206
|
+
* want to navigate from a package outwards.
|
|
207
|
+
*
|
|
208
|
+
* **The containment is read from the tree rather than recomputed from paths.** It used to be an
|
|
209
|
+
* O(n²) sweep comparing every package's directory against every other's and keeping the longest
|
|
210
|
+
* prefix - which is the same question `Workspace.walk` answers on the way down, asked again
|
|
211
|
+
* afterwards with the answer thrown away. Two places deriving one relationship is two places to
|
|
212
|
+
* disagree; there is one now.
|
|
213
|
+
*
|
|
214
|
+
* **`parent` is defined non-enumerably, `children` is a plain field**, which is the one asymmetry
|
|
215
|
+
* here and it is deliberate: a tree is serialized downwards, so `children` has to be walkable and
|
|
216
|
+
* `parent` must not be, or every `JSON.stringify` is a cycle. The same way `Repository.app` and
|
|
217
|
+
* `ORIGINS` travel.
|
|
178
218
|
*
|
|
179
219
|
* A repository's own `repository` is itself, which reads oddly and is the honest answer:
|
|
180
220
|
* `Repository extends Package`, so the repository *is* a package of its own repository.
|
|
181
221
|
*/
|
|
182
|
-
_linkPackages() {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
pkg
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
222
|
+
_linkPackages(tree, nodes, packages) {
|
|
223
|
+
/** Non-enumerable everywhere, including on the repository itself - see `Package.repository`.
|
|
224
|
+
* `writable` because `import` grafts an external repository's packages onto this one. */
|
|
225
|
+
const link = (pkg) => {
|
|
226
|
+
Object.defineProperty(pkg, 'repository', { value: this, enumerable: false, configurable: true, writable: true });
|
|
227
|
+
};
|
|
228
|
+
link(this);
|
|
229
|
+
link(this.rootPackage);
|
|
230
|
+
for (const pkg of this.packages)
|
|
231
|
+
link(pkg);
|
|
232
|
+
/** The walk visits a directory once, so one node is one package and this map is a bijection -
|
|
233
|
+
* which is what lets the edges below be read off the tree instead of guessed from paths. */
|
|
234
|
+
const byNode = new Map(nodes.map((node, i) => [node, packages[i]]));
|
|
235
|
+
byNode.set(tree, this.rootPackage);
|
|
236
|
+
for (const node of [tree, ...nodes]) {
|
|
237
|
+
const pkg = byNode.get(node);
|
|
238
|
+
for (const childNode of node.children) {
|
|
239
|
+
const child = byNode.get(childNode);
|
|
240
|
+
pkg.children.push(child);
|
|
241
|
+
Object.defineProperty(child, 'parent', { value: pkg, enumerable: false, configurable: true });
|
|
198
242
|
}
|
|
199
|
-
pkg.parent = pkg === this.rootPackage ? undefined : parent;
|
|
200
243
|
}
|
|
244
|
+
/** `Repository extends Package` while holding a separate `rootPackage` for the same directory,
|
|
245
|
+
* so both are truthfully the root - they share the one array rather than each getting a copy
|
|
246
|
+
* that could drift. */
|
|
247
|
+
Object.defineProperty(this, 'children', { value: this.rootPackage.children, enumerable: true });
|
|
201
248
|
}
|
|
249
|
+
/**
|
|
250
|
+
* Bakes every package's config: the raw one the workspace cascaded, with every `${{ }}` and value
|
|
251
|
+
* function resolved against that package's scope.
|
|
252
|
+
*/
|
|
253
|
+
/* **The one step the workspace deliberately stops short of.** Resolving an expression needs
|
|
254
|
+
* `pkg`, `repository`, `file` and `git` - which belong to a repository rather than to its layout,
|
|
255
|
+
* and `configScope` is where the measured subtleties of building that scope live.
|
|
256
|
+
*
|
|
257
|
+
* `DEFERRED_PATHS` are left raw, so the resolved config is the only copy anyone needs - `version`
|
|
258
|
+
* reads its own hooks straight off it and interpolates them once `pkg.targetVersion` exists. */
|
|
202
259
|
async _resolveConfigs() {
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
* match it - `"[*]"` and every other name pattern speak only to the packages below - but because
|
|
213
|
-
* `resolveConfig` needs a name to run `matchingSelectors` at all, and `"[/]"` is a selector.
|
|
214
|
-
* Passing none would silently drop the root's own block.
|
|
215
|
-
*/
|
|
216
|
-
const rootRaw = await resolveConfig(this.dirname, this.dirname, cache, this.rootPackage.name);
|
|
217
|
-
this.config = interpolateConfig(rootRaw, this.configScope(this.rootPackage), { skip: DEFERRED_PATHS });
|
|
218
|
-
for (const pkg of this.packages) {
|
|
219
|
-
const raw = await resolveConfig(this.dirname, pkg.dirname, cache, pkg.name);
|
|
220
|
-
pkg.config = interpolateConfig(raw, this.configScope(pkg), { skip: DEFERRED_PATHS });
|
|
221
|
-
}
|
|
260
|
+
const interpolator = new ConfigInterpolator();
|
|
261
|
+
const bake = (pkg) => interpolator.interpolate({
|
|
262
|
+
config: pkg.rawConfig,
|
|
263
|
+
scope: this.configScope(pkg),
|
|
264
|
+
skip: DEFERRED_PATHS,
|
|
265
|
+
});
|
|
266
|
+
this.config = bake(this.rootPackage);
|
|
267
|
+
for (const pkg of this.packages)
|
|
268
|
+
pkg.config = bake(pkg);
|
|
222
269
|
if (this.monorepo)
|
|
223
270
|
this.rootPackage.config = this.config;
|
|
224
271
|
}
|
|
@@ -236,7 +283,7 @@ export class Repository extends Package {
|
|
|
236
283
|
// than refusing it - so the scope has to survive one too.
|
|
237
284
|
const name = pkg.name ?? '';
|
|
238
285
|
/** Splitting `@scope/name` is npm's convention, not a universal - the provider decides. */
|
|
239
|
-
const { scope: nameScope, unscopedName } = Manifest.splitName(pkg.dirname, name);
|
|
286
|
+
const { scope: nameScope, unscopedName } = Manifest.splitName(this.app, pkg.dirname, name);
|
|
240
287
|
const scope = {
|
|
241
288
|
name,
|
|
242
289
|
scope: nameScope,
|
|
@@ -384,34 +431,107 @@ export class Repository extends Package {
|
|
|
384
431
|
* otherwise: the plugins that know what a package is are named in the config file this step
|
|
385
432
|
* is looking for.
|
|
386
433
|
* 2. **Load the plugins** the root's config names, which registers their workspace providers
|
|
387
|
-
* (
|
|
434
|
+
* (their commands arrive through `.rmanrc "commands"`, which `cli.ts` reads).
|
|
388
435
|
* 3. **Ask the providers** for the layout. None recognizing it means a repository that is itself
|
|
389
436
|
* the one package.
|
|
390
437
|
*
|
|
391
|
-
* **A repository
|
|
438
|
+
* **A repository no technology recognizes has no packages beyond itself**, and that is the
|
|
392
439
|
* boundary working rather than failing: `workspaces` in a `package.json` is npm's idea, so it
|
|
393
|
-
* takes
|
|
440
|
+
* takes a platform that reads one for the directory to be a workspace at all. rman's own presets
|
|
441
|
+
* are laid under every root (see `DEFAULT_PRESETS`), so a plain Node repository needs no config
|
|
442
|
+
* file to say so; anything else arrives through `extends` or `platforms`.
|
|
394
443
|
*/
|
|
444
|
+
/* **`presets` is an opt-out, and it exists for the specs that must not have one.** rman's own
|
|
445
|
+
* presets are laid under every root, so a caller that brought its own ecosystem would otherwise
|
|
446
|
+
* get the `node` technology, `clean`, `ci` and the npm publish target beside it. That is right
|
|
447
|
+
* for a repository and wrong for a synthetic one: measured, the core's `publish` specs ran a
|
|
448
|
+
* **real `npm publish` against registry.npmjs.org**, and only a 404 stopped it - the failure
|
|
449
|
+
* `useLocalBin`'s doc already records for `docker`, arriving by a new route. `test/_fixture.ts`
|
|
450
|
+
* passes `[]`. */
|
|
395
451
|
static async create(root, options) {
|
|
396
452
|
const from = root || process.cwd();
|
|
397
453
|
const rootDir = Workspace.findRoot(from, options?.deep ?? 10);
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
454
|
+
const app = options?.app ?? new RmanApplication();
|
|
455
|
+
/**
|
|
456
|
+
* **The whole of discovery and config reading is the workspace's.** It reads each level's
|
|
457
|
+
* config, loads whatever that level names, decides which technology claims each directory,
|
|
458
|
+
* assigns the selectors and cascades every package's **raw** config.
|
|
459
|
+
*
|
|
460
|
+
* Technologies already on the application are handed over: a programmatic caller - and every
|
|
461
|
+
* spec in this suite - registers one without writing a config, and the walk has to see it.
|
|
462
|
+
*/
|
|
463
|
+
const workspace = await Workspace.create(rootDir, {
|
|
464
|
+
app,
|
|
465
|
+
deep: options?.deep,
|
|
466
|
+
platforms: [...app.platforms],
|
|
467
|
+
presets: options?.presets,
|
|
468
|
+
});
|
|
469
|
+
/** What the configs named, onto the application the commands read from. */
|
|
470
|
+
for (const platform of workspace.platforms)
|
|
471
|
+
app.platforms.add(platform);
|
|
472
|
+
for (const plugin of workspace.plugins) {
|
|
473
|
+
app.plugins.add(plugin);
|
|
474
|
+
await plugin.afterInitApplication?.({ app });
|
|
475
|
+
}
|
|
476
|
+
/** One loop, because `workspace.platforms` already holds every technology in play whatever
|
|
477
|
+
* route it arrived by. There used to be a second one over each plugin's own `platforms`, and
|
|
478
|
+
* by the end it was pure duplication of this. */
|
|
479
|
+
for (const platform of workspace.platforms) {
|
|
480
|
+
if (platform.versionPlanner)
|
|
481
|
+
app.versionPlanner = platform.versionPlanner;
|
|
482
|
+
}
|
|
483
|
+
/** **Publish targets the root config declared.** `publish` builds its `--target` choices from
|
|
484
|
+
* `app.publishTargets` when its factory runs, so they have to be there before the commands
|
|
485
|
+
* are. Read off the root's own level rather than a package's: a target is the repository's. */
|
|
486
|
+
for (const target of toList(workspace.rootPackage.rawConfig.publishTargets)) {
|
|
487
|
+
if (!target || typeof target !== 'object' || typeof target.name !== 'string') {
|
|
488
|
+
throw new Error(`"publishTargets" takes a target or a glob naming modules that export one - got ` +
|
|
489
|
+
`${target === null ? 'null' : typeof target}.`);
|
|
490
|
+
}
|
|
491
|
+
app.publishTargets.add(target);
|
|
492
|
+
}
|
|
493
|
+
const packages = workspace.packages;
|
|
494
|
+
const repo = new Repository(app, rootDir, packages.length > 0, packages, from, workspace.rootPackage.platform);
|
|
495
|
+
repo._adopt(workspace);
|
|
496
|
+
app.attachRepository(repo);
|
|
407
497
|
return Repository._init(repo);
|
|
408
498
|
}
|
|
409
|
-
/**
|
|
410
|
-
*
|
|
411
|
-
*
|
|
499
|
+
/**
|
|
500
|
+
* Takes the workspace's packages as this repository's own - the back-references a `Package` needs
|
|
501
|
+
* and the parent/child edges the walk established.
|
|
502
|
+
*/
|
|
503
|
+
/* `Repository extends Package` while the workspace built its own root package for the same
|
|
504
|
+
* directory, so both are, truthfully, the root. This keeps the workspace's - it is the one the
|
|
505
|
+
* walk handed a platform and a selector - and leaves `Repository` itself pointing at the same
|
|
506
|
+
* children. */
|
|
507
|
+
_adopt(workspace) {
|
|
508
|
+
const link = (pkg) => void Object.defineProperty(pkg, 'repository', {
|
|
509
|
+
value: this,
|
|
510
|
+
enumerable: false,
|
|
511
|
+
configurable: true,
|
|
512
|
+
writable: true,
|
|
513
|
+
});
|
|
514
|
+
link(this);
|
|
515
|
+
this.rootPackage = workspace.rootPackage;
|
|
516
|
+
link(this.rootPackage);
|
|
517
|
+
for (const pkg of this.packages)
|
|
518
|
+
link(pkg);
|
|
519
|
+
if (!this.monorepo)
|
|
520
|
+
this.packages = [this.rootPackage];
|
|
521
|
+
Object.defineProperty(this, 'children', { value: this.rootPackage.children, enumerable: true });
|
|
522
|
+
}
|
|
412
523
|
static async _init(repo) {
|
|
413
524
|
await repo._resolveConfigs();
|
|
414
525
|
repo._updateDependencies();
|
|
526
|
+
/** **Last, and that is the whole point of the second stage.** Every package is found, every
|
|
527
|
+
* config is baked and the dependency graph is linked - so a plugin handed this repository can
|
|
528
|
+
* read it rather than being told to wait for a command it may not contribute. */
|
|
529
|
+
for (const plugin of repo.app.plugins)
|
|
530
|
+
await plugin.afterInitRepository?.({ app: repo.app, repository: repo });
|
|
415
531
|
return repo;
|
|
416
532
|
}
|
|
417
533
|
}
|
|
534
|
+
/** A key that takes one value or a list of them, as a list. */
|
|
535
|
+
function toList(value) {
|
|
536
|
+
return value === undefined ? [] : Array.isArray(value) ? value : [value];
|
|
537
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Logger } from '../../utils/logger.js';
|
|
2
|
+
import type { RmanApplication } from '../application.js';
|
|
3
|
+
import type { Repository } from './repository.js';
|
|
4
|
+
/**
|
|
5
|
+
* **The one service shape.** Everything that does work on a repository is a class extending this,
|
|
6
|
+
* constructed once per `RmanApplication` and reached through `app.getService(name)`.
|
|
7
|
+
*
|
|
8
|
+
* rman's services were `export namespace` blocks, and the split between those and the two classes
|
|
9
|
+
* (`VersionPlanService`, `VersionScheme`) was historical rather than principled: whatever needed to
|
|
10
|
+
* be *extended* became a class, and the rest stayed a namespace. Three consequences, all of them
|
|
11
|
+
* measured in this repository:
|
|
12
|
+
*
|
|
13
|
+
* - **A namespace that needs state can only have global state.** `RunService` is a namespace and
|
|
14
|
+
* acquired three module-level variables - `stepSources` and two "already warned" sets - which is
|
|
15
|
+
* why a warning printed for one repository was suppressed for the next one in the same process.
|
|
16
|
+
* - **A namespace cannot be extended.** The two seams a plugin has to specialize
|
|
17
|
+
* (`NodeVersionPlanService extends VersionPlanService`) are precisely the two that are classes.
|
|
18
|
+
* - **A namespace has to be stubbed by mutating the module**, and CLAUDE.md records that failing:
|
|
19
|
+
* a spec that captured a core function at module scope and restored it put the *un-augmented*
|
|
20
|
+
* version back for the rest of the process and broke a spec two files away. An instance is
|
|
21
|
+
* rebuilt per application, so a stub dies with it.
|
|
22
|
+
*
|
|
23
|
+
* **`repository` is no longer a parameter.** Nearly every service method took it first; it now
|
|
24
|
+
* comes from the application, which is what made the argument redundant in the first place.
|
|
25
|
+
*
|
|
26
|
+
* Pure functions of their arguments stay plain exported functions rather than becoming services -
|
|
27
|
+
* `ConventionalCommitsService.parseSubject('feat: x')` parses a string and will never hold state,
|
|
28
|
+
* and making it `app.getService('conventionalCommits').parseSubject(...)` would be ceremony that
|
|
29
|
+
* also puts an application between a caller and a parser. The line is whether it needs the
|
|
30
|
+
* repository.
|
|
31
|
+
*/
|
|
32
|
+
export declare abstract class Service {
|
|
33
|
+
protected readonly app: RmanApplication;
|
|
34
|
+
constructor(app: RmanApplication);
|
|
35
|
+
protected get repository(): Repository;
|
|
36
|
+
protected get logger(): Logger;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Every service the application can hand out, by name.
|
|
40
|
+
*
|
|
41
|
+
* Declaration-merged, like `RmanConfigKeys`: the core declares its own here, and a plugin adds its
|
|
42
|
+
* own from its own package with `declare module 'rman'`. That is what keeps `getService` typed -
|
|
43
|
+
* a bare `Map<string, Service>` would need a cast at every call and would turn a misspelled name
|
|
44
|
+
* into a runtime failure instead of a compile error.
|
|
45
|
+
*/
|
|
46
|
+
export interface ServiceMap {
|
|
47
|
+
}
|
|
48
|
+
/** How a service is built when the application first needs it. */
|
|
49
|
+
export type ServiceFactory<T> = (app: RmanApplication) => T;
|