@goodbones/cli 0.1.0-beta.16 → 0.1.0-beta.18
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/build/dts/campaigns/host.d.ts +51 -0
- package/build/dts/campaigns/host.d.ts.map +1 -0
- package/build/dts/campaigns/live.d.ts +3 -0
- package/build/dts/campaigns/live.d.ts.map +1 -0
- package/build/dts/campaigns/none.d.ts +7 -0
- package/build/dts/campaigns/none.d.ts.map +1 -0
- package/build/dts/campaigns/verbs.d.ts +6 -0
- package/build/dts/campaigns/verbs.d.ts.map +1 -0
- package/build/dts/config-loader.d.ts +7 -5
- package/build/dts/config-loader.d.ts.map +1 -1
- package/build/dts/output.d.ts +9 -0
- package/build/dts/output.d.ts.map +1 -0
- package/build/dts/run.d.ts +3 -18
- package/build/dts/run.d.ts.map +1 -1
- package/build/esm/campaigns/host.js +2 -0
- package/build/esm/campaigns/host.js.map +1 -0
- package/build/esm/campaigns/live.js +106 -0
- package/build/esm/campaigns/live.js.map +1 -0
- package/build/esm/campaigns/none.js +34 -0
- package/build/esm/campaigns/none.js.map +1 -0
- package/build/esm/campaigns/verbs.js +489 -0
- package/build/esm/campaigns/verbs.js.map +1 -0
- package/build/esm/config-loader.js +40 -25
- package/build/esm/config-loader.js.map +1 -1
- package/build/esm/infer.js +3 -3
- package/build/esm/infer.js.map +1 -1
- package/build/esm/output.js +8 -0
- package/build/esm/output.js.map +1 -0
- package/build/esm/run.js +39 -555
- package/build/esm/run.js.map +1 -1
- package/package.json +18 -6
- package/src/campaigns/host.ts +120 -0
- package/src/campaigns/live.ts +136 -0
- package/src/campaigns/none.ts +47 -0
- package/src/campaigns/verbs.ts +620 -0
- package/src/config-loader.ts +41 -26
- package/src/infer.ts +3 -3
- package/src/output.ts +18 -0
- package/src/run.ts +56 -726
package/src/config-loader.ts
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
|
|
3
|
-
import { astGrepMatcher } from "@goodbones/ast-grep";
|
|
4
|
-
import {
|
|
5
|
-
campaignsExtension,
|
|
6
|
-
loadCampaignFunctions,
|
|
7
|
-
makeReportSourceLive,
|
|
8
|
-
} from "@goodbones/campaigns";
|
|
9
3
|
import {
|
|
10
4
|
type ConfigInvalid,
|
|
11
5
|
findManifestFile,
|
|
6
|
+
importOptional,
|
|
12
7
|
type Language,
|
|
13
|
-
type LoadedPolicy,
|
|
14
8
|
loadPolicy,
|
|
15
9
|
makeFileSystemLive,
|
|
16
10
|
type PatternInvalid,
|
|
@@ -19,15 +13,28 @@ import {
|
|
|
19
13
|
import { typescriptLanguage } from "@goodbones/typescript";
|
|
20
14
|
import * as Result from "effect/Result";
|
|
21
15
|
|
|
22
|
-
|
|
16
|
+
import type { CampaignsFamily, LoadedPolicy } from "./campaigns/host.js";
|
|
17
|
+
import { campaignsNotInstalled } from "./campaigns/none.js";
|
|
18
|
+
|
|
19
|
+
export type { LoadedPolicy } from "./campaigns/host.js";
|
|
23
20
|
|
|
24
21
|
// The language packs this host is composed with. Constructed here and handed
|
|
25
22
|
// down as the `Language` port, so nothing below this file names TypeScript —
|
|
26
|
-
// or ast-grep,
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
// or ast-grep, the syntax matcher for the campaigns family's `syntax` term.
|
|
24
|
+
// The matcher is an optional peer, with a native binary: composed into the
|
|
25
|
+
// pack when it is installed, and a campaign that needs it without it is
|
|
26
|
+
// refused at load, naming the package.
|
|
27
|
+
export const hostLanguages = async (): Promise<ReadonlyArray<Language>> => {
|
|
28
|
+
const astGrep = await importOptional(() => import("@goodbones/ast-grep"), "@goodbones/ast-grep");
|
|
29
|
+
return [typescriptLanguage(astGrep === null ? {} : { syntax: astGrep.astGrepMatcher() })];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// The campaigns family, when `@goodbones/campaigns` is installed beside this
|
|
33
|
+
// host, and `none` when it is not. Everything that names the package is
|
|
34
|
+
// under `campaigns/`, reached through this one import.
|
|
35
|
+
export const hostCampaigns = async (): Promise<CampaignsFamily> =>
|
|
36
|
+
(await importOptional(() => import("./campaigns/live.js"), "@goodbones/campaigns"))
|
|
37
|
+
?.liveCampaigns ?? campaignsNotInstalled;
|
|
31
38
|
|
|
32
39
|
// The clock the campaigns are judged by. `ARCHITECTURE_NOW` (an ISO date or
|
|
33
40
|
// epoch milliseconds) pins it, for a CI that replays a day or a test that
|
|
@@ -53,43 +60,51 @@ export const manifestPathOf = (repoRoot: string, configFilename?: string): strin
|
|
|
53
60
|
? findManifestFile(repoRoot)
|
|
54
61
|
: path.resolve(repoRoot, configFilename);
|
|
55
62
|
|
|
63
|
+
// `given` is the family to compose: found by `hostCampaigns` unless a
|
|
64
|
+
// test names one, which is how the host is run as if the package were absent.
|
|
56
65
|
export const loadPolicyFromFile = async (
|
|
57
66
|
repoRoot: string,
|
|
58
67
|
configFilename?: string,
|
|
68
|
+
given?: CampaignsFamily,
|
|
59
69
|
): Promise<LoadedPolicy> => {
|
|
60
70
|
const configPath = manifestPathOf(repoRoot, configFilename);
|
|
61
71
|
const read = await readManifestFile(configPath);
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const { functions, manifest } = await loadCampaignFunctions(configPath, read.manifest);
|
|
72
|
+
const campaigns = given ?? (await hostCampaigns());
|
|
73
|
+
const composed = await campaigns.compose(repoRoot, configPath, read.manifest);
|
|
65
74
|
const loaded = loadPolicy({
|
|
66
75
|
repoRoot,
|
|
67
76
|
configPath,
|
|
68
|
-
manifest,
|
|
77
|
+
manifest: composed.manifest,
|
|
69
78
|
locate: read.locate,
|
|
70
|
-
languages: hostLanguages(),
|
|
79
|
+
languages: await hostLanguages(),
|
|
71
80
|
fileSystem: makeFileSystemLive(repoRoot),
|
|
72
|
-
extensions:
|
|
81
|
+
extensions: composed.extensions,
|
|
82
|
+
uninstalled: composed.uninstalled,
|
|
73
83
|
now: hostNow(),
|
|
74
84
|
});
|
|
75
85
|
if (Result.isFailure(loaded)) throw loaded.failure;
|
|
76
|
-
return loaded.success;
|
|
86
|
+
return { ...loaded.success, campaigns: campaigns.host };
|
|
77
87
|
};
|
|
78
88
|
|
|
79
89
|
// The same composition, for a manifest the host already holds as a value
|
|
80
90
|
// rather than a file: the bootstrap `infer` resolves through, and the
|
|
81
91
|
// manifest it then proves loads. Failure is returned, not thrown — the caller
|
|
82
92
|
// has a sentence to put around it.
|
|
83
|
-
export const loadPolicyFromManifest = (
|
|
93
|
+
export const loadPolicyFromManifest = async (
|
|
84
94
|
repoRoot: string,
|
|
85
95
|
manifest: unknown,
|
|
86
|
-
): Result.Result<LoadedPolicy, ConfigInvalid | PatternInvalid
|
|
87
|
-
|
|
96
|
+
): Promise<Result.Result<LoadedPolicy, ConfigInvalid | PatternInvalid>> => {
|
|
97
|
+
const campaigns = await hostCampaigns();
|
|
98
|
+
const composed = await campaigns.compose(repoRoot, null, manifest);
|
|
99
|
+
const loaded = loadPolicy({
|
|
88
100
|
repoRoot,
|
|
89
101
|
configPath: path.resolve(repoRoot, "architecture.yaml"),
|
|
90
|
-
manifest,
|
|
91
|
-
languages: hostLanguages(),
|
|
102
|
+
manifest: composed.manifest,
|
|
103
|
+
languages: await hostLanguages(),
|
|
92
104
|
fileSystem: makeFileSystemLive(repoRoot),
|
|
93
|
-
extensions:
|
|
105
|
+
extensions: composed.extensions,
|
|
106
|
+
uninstalled: composed.uninstalled,
|
|
94
107
|
now: hostNow(),
|
|
95
108
|
});
|
|
109
|
+
return Result.map(loaded, (policy) => ({ ...policy, campaigns: campaigns.host }));
|
|
110
|
+
};
|
package/src/infer.ts
CHANGED
|
@@ -237,13 +237,13 @@ const settingOf = async (
|
|
|
237
237
|
const roots =
|
|
238
238
|
flags.roots.length > 0
|
|
239
239
|
? flags.roots.map(normalizeRoot)
|
|
240
|
-
: rootsOfRepository(repoRoot, hostLanguages());
|
|
240
|
+
: rootsOfRepository(repoRoot, await hostLanguages());
|
|
241
241
|
if (roots.length === 0) {
|
|
242
242
|
return Result.fail(
|
|
243
243
|
"no source files found under any top-level folder. Name the folder to describe with --root <dir>.",
|
|
244
244
|
);
|
|
245
245
|
}
|
|
246
|
-
const loaded = loadPolicyFromManifest(repoRoot, bootstrapManifest(roots, tsconfig));
|
|
246
|
+
const loaded = await loadPolicyFromManifest(repoRoot, bootstrapManifest(roots, tsconfig));
|
|
247
247
|
if (Result.isFailure(loaded)) return Result.fail(String(loaded.failure));
|
|
248
248
|
return Result.succeed({
|
|
249
249
|
policy: loaded.success,
|
|
@@ -449,7 +449,7 @@ export const infer = (
|
|
|
449
449
|
|
|
450
450
|
// The proof: what was written loads, every probe passes, and the floors
|
|
451
451
|
// are the numbers it reaches today.
|
|
452
|
-
const loaded = loadPolicyFromManifest(repoRoot, inferred.manifest);
|
|
452
|
+
const loaded = yield* Effect.promise(() => loadPolicyFromManifest(repoRoot, inferred.manifest));
|
|
453
453
|
if (Result.isFailure(loaded)) {
|
|
454
454
|
return yield* Effect.fail(
|
|
455
455
|
fail(
|
package/src/output.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
|
|
3
|
+
// What every command shares: how it fails, and how it writes. Its own module
|
|
4
|
+
// so the campaigns glue under `campaigns/` speaks the same way without
|
|
5
|
+
// reaching `run.ts`, which would close a cycle through the import that loads
|
|
6
|
+
// the glue.
|
|
7
|
+
|
|
8
|
+
export type CliFailure = { readonly _tag: "CliFailure"; readonly message: string };
|
|
9
|
+
|
|
10
|
+
export const fail = (message: string): CliFailure => ({ _tag: "CliFailure", message });
|
|
11
|
+
|
|
12
|
+
export const report = (lines: ReadonlyArray<string>): Effect.Effect<void> =>
|
|
13
|
+
Effect.sync(() => {
|
|
14
|
+
for (const line of lines) process.stdout.write(`${line}\n`);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export const count = (n: number, noun: string, plural = `${noun}s`): string =>
|
|
18
|
+
`${String(n)} ${n === 1 ? noun : plural}`;
|