@goodbones/core 0.1.0-beta.1 → 0.1.0-beta.2
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/domain/manifest-location.d.ts +7 -0
- package/build/dts/domain/manifest-location.d.ts.map +1 -0
- package/build/dts/index.d.ts +5 -2
- package/build/dts/index.d.ts.map +1 -1
- package/build/dts/infrastructure/manifest-file.d.ts +10 -2
- package/build/dts/infrastructure/manifest-file.d.ts.map +1 -1
- package/build/dts/load/policy.d.ts +2 -0
- package/build/dts/load/policy.d.ts.map +1 -1
- package/build/dts/manifest/expand.d.ts +26 -0
- package/build/dts/manifest/expand.d.ts.map +1 -0
- package/build/dts/manifest/json-schema.d.ts +8 -0
- package/build/dts/manifest/json-schema.d.ts.map +1 -0
- package/build/dts/manifest/manifest.d.ts +5 -1
- package/build/dts/manifest/manifest.d.ts.map +1 -1
- package/build/esm/domain/manifest-location.js +6 -0
- package/build/esm/domain/manifest-location.js.map +1 -0
- package/build/esm/index.js +3 -1
- package/build/esm/index.js.map +1 -1
- package/build/esm/infrastructure/manifest-file.js +144 -7
- package/build/esm/infrastructure/manifest-file.js.map +1 -1
- package/build/esm/load/policy.js +1 -1
- package/build/esm/load/policy.js.map +1 -1
- package/build/esm/manifest/expand.js +111 -0
- package/build/esm/manifest/expand.js.map +1 -0
- package/build/esm/manifest/json-schema.js +76 -0
- package/build/esm/manifest/json-schema.js.map +1 -0
- package/build/esm/manifest/manifest.js +69 -4
- package/build/esm/manifest/manifest.js.map +1 -1
- package/package.json +7 -3
- package/schema/architecture.schema.json +1089 -0
- package/src/domain/manifest-location.ts +19 -0
- package/src/index.ts +22 -1
- package/src/infrastructure/manifest-file.ts +184 -8
- package/src/load/policy.ts +5 -1
- package/src/manifest/expand.ts +174 -0
- package/src/manifest/json-schema.ts +99 -0
- package/src/manifest/manifest.ts +110 -9
package/src/manifest/manifest.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import * as Result from "effect/Result";
|
|
2
2
|
import * as Schema from "effect/Schema";
|
|
3
|
+
import * as SchemaIssue from "effect/SchemaIssue";
|
|
3
4
|
|
|
4
5
|
import { DeclarationKind, ResolveConfig } from "../domain/architecture-config.js";
|
|
5
6
|
import { ConfigInvalid } from "../domain/architecture-error.js";
|
|
7
|
+
import type { ManifestLocator, ManifestPath } from "../domain/manifest-location.js";
|
|
8
|
+
import { expandManifest, originOf, type Substitution } from "./expand.js";
|
|
6
9
|
|
|
7
10
|
// A manifest is a tree of nodes keyed by path pattern, where everything the
|
|
8
11
|
// architecture says about a part of the tree is written at that part of the tree.
|
|
@@ -286,7 +289,12 @@ export type ExportRestriction = typeof ExportRestriction.Type;
|
|
|
286
289
|
export const globsOf = (globs: string | ReadonlyArray<string>): ReadonlyArray<string> =>
|
|
287
290
|
typeof globs === "string" ? [globs] : globs;
|
|
288
291
|
|
|
289
|
-
|
|
292
|
+
// Every issue, not the first: a manifest is edited by hand, and the reader
|
|
293
|
+
// fixing one line wants to know about the other three. A key the schema does
|
|
294
|
+
// not declare is refused rather than dropped — a misspelled `matchNot` that
|
|
295
|
+
// decoded to nothing would be a rule quietly enforcing less than it says.
|
|
296
|
+
const decode = Schema.decodeUnknownResult(Manifest, { errors: "all", onExcessProperty: "error" });
|
|
297
|
+
const flatten = SchemaIssue.makeFormatterStandardSchemaV1();
|
|
290
298
|
|
|
291
299
|
export type DecodedManifest = {
|
|
292
300
|
readonly manifest: Manifest;
|
|
@@ -392,17 +400,110 @@ const normalizeLegacyMembers = (
|
|
|
392
400
|
return { input: { ...input, tree }, notices };
|
|
393
401
|
};
|
|
394
402
|
|
|
403
|
+
export type DecodeManifestOptions = {
|
|
404
|
+
// Turns a path in the file into a line and column. The YAML reader supplies
|
|
405
|
+
// one; a JavaScript module has no positions to give and passes nothing.
|
|
406
|
+
readonly locate?: ManifestLocator | undefined;
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
const isIdentifier = (key: string): boolean => /^[A-Za-z_$][\w$]*$/.test(key);
|
|
410
|
+
|
|
411
|
+
// `tree["~/core/"].members[0].subject` — dotted where a key reads as a name,
|
|
412
|
+
// bracketed where it does not, so a node key that is a path pattern stays
|
|
413
|
+
// legible.
|
|
414
|
+
const renderPath = (path: ManifestPath): string =>
|
|
415
|
+
path.length === 0
|
|
416
|
+
? "(root)"
|
|
417
|
+
: path
|
|
418
|
+
.map((segment, index) => {
|
|
419
|
+
if (typeof segment === "number") return `[${String(segment)}]`;
|
|
420
|
+
const key = String(segment);
|
|
421
|
+
if (isIdentifier(key)) return index === 0 ? key : `.${key}`;
|
|
422
|
+
return `[${JSON.stringify(key)}]`;
|
|
423
|
+
})
|
|
424
|
+
.join("");
|
|
425
|
+
|
|
426
|
+
const fileLabelOf = (configPath: string): string => configPath.split(/[\\/]/).at(-1) ?? configPath;
|
|
427
|
+
|
|
428
|
+
const positionOf = (
|
|
429
|
+
file: string,
|
|
430
|
+
locate: ManifestLocator | undefined,
|
|
431
|
+
path: ManifestPath,
|
|
432
|
+
): string | null => {
|
|
433
|
+
const found = locate?.(path) ?? null;
|
|
434
|
+
return found === null ? null : `${file}:${String(found.line)}:${String(found.column)}`;
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
// One line per issue: where in the file, which path, what was wrong — and,
|
|
438
|
+
// when the value came in through a `use`, the reference that pulled it in,
|
|
439
|
+
// since the fragment's own line may sit far from where the reader is looking.
|
|
440
|
+
const describeIssue = (
|
|
441
|
+
configPath: string,
|
|
442
|
+
locate: ManifestLocator | undefined,
|
|
443
|
+
substitutions: ReadonlyArray<Substitution>,
|
|
444
|
+
path: ManifestPath,
|
|
445
|
+
detail: string,
|
|
446
|
+
): string => {
|
|
447
|
+
const file = fileLabelOf(configPath);
|
|
448
|
+
const origin = originOf(substitutions, path);
|
|
449
|
+
const at = positionOf(file, locate, origin.path);
|
|
450
|
+
const via = origin.via.map(({ at: ref, name }) => {
|
|
451
|
+
const position = positionOf(file, locate, ref);
|
|
452
|
+
return `via \`use: ${JSON.stringify(name)}\`${position === null ? "" : ` at ${position}`}`;
|
|
453
|
+
});
|
|
454
|
+
return (
|
|
455
|
+
` ${at === null ? "" : `${at} `}${renderPath(origin.path)}: ${detail}` +
|
|
456
|
+
(via.length === 0 ? "" : ` (${via.join(", ")})`)
|
|
457
|
+
);
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
// The standard-schema formatter flattens the issue tree to `{ path, message }`
|
|
461
|
+
// pairs; a path segment may arrive wrapped as `{ key }`.
|
|
462
|
+
const pathOf = (issue: {
|
|
463
|
+
readonly path?: ReadonlyArray<PropertyKey | { readonly key: PropertyKey }> | undefined;
|
|
464
|
+
}): ManifestPath =>
|
|
465
|
+
(issue.path ?? []).map((segment) => (typeof segment === "object" ? segment.key : segment));
|
|
466
|
+
|
|
395
467
|
export const decodeManifest = (
|
|
396
468
|
configPath: string,
|
|
397
469
|
input: unknown,
|
|
470
|
+
options: DecodeManifestOptions = {},
|
|
398
471
|
): Result.Result<DecodedManifest, ConfigInvalid> => {
|
|
399
|
-
const
|
|
472
|
+
const expanded = expandManifest(input);
|
|
473
|
+
if (Result.isFailure(expanded)) {
|
|
474
|
+
return Result.fail(
|
|
475
|
+
new ConfigInvalid({
|
|
476
|
+
configPath,
|
|
477
|
+
detail:
|
|
478
|
+
"the manifest does not expand:\n" +
|
|
479
|
+
describeIssue(
|
|
480
|
+
configPath,
|
|
481
|
+
options.locate,
|
|
482
|
+
[],
|
|
483
|
+
expanded.failure.path,
|
|
484
|
+
expanded.failure.detail,
|
|
485
|
+
),
|
|
486
|
+
}),
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
const { substitutions, value } = expanded.success;
|
|
490
|
+
|
|
491
|
+
const resolve = normalizeLegacyResolve(value);
|
|
400
492
|
const members = normalizeLegacyMembers(resolve.input);
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
(
|
|
405
|
-
)
|
|
406
|
-
|
|
407
|
-
|
|
493
|
+
const decoded = decode(members.input);
|
|
494
|
+
if (Result.isFailure(decoded)) {
|
|
495
|
+
const lines = flatten(decoded.failure.issue).issues.map((issue) =>
|
|
496
|
+
describeIssue(configPath, options.locate, substitutions, pathOf(issue), issue.message),
|
|
497
|
+
);
|
|
498
|
+
return Result.fail(
|
|
499
|
+
new ConfigInvalid({
|
|
500
|
+
configPath,
|
|
501
|
+
detail: `the manifest does not decode:\n${lines.join("\n")}`,
|
|
502
|
+
}),
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
return Result.succeed({
|
|
506
|
+
manifest: decoded.success,
|
|
507
|
+
notices: [...resolve.notices, ...members.notices],
|
|
508
|
+
});
|
|
408
509
|
};
|