@tulipes/core 0.1.0
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/dist/acl/acl.d.ts +49 -0
- package/dist/acl/acl.js +123 -0
- package/dist/acl/acl.js.map +1 -0
- package/dist/acl/index.d.ts +2 -0
- package/dist/acl/index.js +3 -0
- package/dist/acl/index.js.map +1 -0
- package/dist/acl/load-acl.d.ts +11 -0
- package/dist/acl/load-acl.js +38 -0
- package/dist/acl/load-acl.js.map +1 -0
- package/dist/boot/app-files.d.ts +17 -0
- package/dist/boot/app-files.js +31 -0
- package/dist/boot/app-files.js.map +1 -0
- package/dist/boot/banner.d.ts +9 -0
- package/dist/boot/banner.js +82 -0
- package/dist/boot/banner.js.map +1 -0
- package/dist/boot/boot.d.ts +32 -0
- package/dist/boot/boot.js +212 -0
- package/dist/boot/boot.js.map +1 -0
- package/dist/boot/contracts.d.ts +47 -0
- package/dist/boot/contracts.js +2 -0
- package/dist/boot/contracts.js.map +1 -0
- package/dist/boot/import-file.d.ts +7 -0
- package/dist/boot/import-file.js +23 -0
- package/dist/boot/import-file.js.map +1 -0
- package/dist/boot/index.d.ts +3 -0
- package/dist/boot/index.js +2 -0
- package/dist/boot/index.js.map +1 -0
- package/dist/cli/bin.d.ts +2 -0
- package/dist/cli/bin.js +7 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/dev.d.ts +8 -0
- package/dist/cli/dev.js +22 -0
- package/dist/cli/dev.js.map +1 -0
- package/dist/cli/env-check.d.ts +6 -0
- package/dist/cli/env-check.js +28 -0
- package/dist/cli/env-check.js.map +1 -0
- package/dist/cli/main.d.ts +1 -0
- package/dist/cli/main.js +45 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/new-module.d.ts +6 -0
- package/dist/cli/new-module.js +84 -0
- package/dist/cli/new-module.js.map +1 -0
- package/dist/cli/sync.d.ts +12 -0
- package/dist/cli/sync.js +117 -0
- package/dist/cli/sync.js.map +1 -0
- package/dist/config/build-config.d.ts +13 -0
- package/dist/config/build-config.js +63 -0
- package/dist/config/build-config.js.map +1 -0
- package/dist/config/index.d.ts +13 -0
- package/dist/config/index.js +2 -0
- package/dist/config/index.js.map +1 -0
- package/dist/db/database.d.ts +25 -0
- package/dist/db/database.js +111 -0
- package/dist/db/database.js.map +1 -0
- package/dist/db/index.d.ts +2 -0
- package/dist/db/index.js +3 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/model-store.d.ts +22 -0
- package/dist/db/model-store.js +37 -0
- package/dist/db/model-store.js.map +1 -0
- package/dist/env/coerce.d.ts +17 -0
- package/dist/env/coerce.js +53 -0
- package/dist/env/coerce.js.map +1 -0
- package/dist/env/environment.d.ts +42 -0
- package/dist/env/environment.js +117 -0
- package/dist/env/environment.js.map +1 -0
- package/dist/env/index.d.ts +14 -0
- package/dist/env/index.js +5 -0
- package/dist/env/index.js.map +1 -0
- package/dist/env/meta-discovery.d.ts +17 -0
- package/dist/env/meta-discovery.js +65 -0
- package/dist/env/meta-discovery.js.map +1 -0
- package/dist/env/spec.d.ts +133 -0
- package/dist/env/spec.js +63 -0
- package/dist/env/spec.js.map +1 -0
- package/dist/env/variable-store.d.ts +26 -0
- package/dist/env/variable-store.js +49 -0
- package/dist/env/variable-store.js.map +1 -0
- package/dist/errors/boot-error.d.ts +28 -0
- package/dist/errors/boot-error.js +44 -0
- package/dist/errors/boot-error.js.map +1 -0
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.js +2 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/http/error-handler.d.ts +23 -0
- package/dist/http/error-handler.js +48 -0
- package/dist/http/error-handler.js.map +1 -0
- package/dist/http/index.d.ts +1 -0
- package/dist/http/index.js +2 -0
- package/dist/http/index.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/discovery.d.ts +10 -0
- package/dist/modules/discovery.js +91 -0
- package/dist/modules/discovery.js.map +1 -0
- package/dist/modules/explorer.d.ts +38 -0
- package/dist/modules/explorer.js +64 -0
- package/dist/modules/explorer.js.map +1 -0
- package/dist/modules/graph.d.ts +16 -0
- package/dist/modules/graph.js +80 -0
- package/dist/modules/graph.js.map +1 -0
- package/dist/modules/index.d.ts +4 -0
- package/dist/modules/index.js +5 -0
- package/dist/modules/index.js.map +1 -0
- package/dist/modules/manifest.d.ts +43 -0
- package/dist/modules/manifest.js +26 -0
- package/dist/modules/manifest.js.map +1 -0
- package/dist/queues/index.d.ts +2 -0
- package/dist/queues/index.js +3 -0
- package/dist/queues/index.js.map +1 -0
- package/dist/queues/load-queues.d.ts +12 -0
- package/dist/queues/load-queues.js +56 -0
- package/dist/queues/load-queues.js.map +1 -0
- package/dist/queues/queue-manager.d.ts +44 -0
- package/dist/queues/queue-manager.js +134 -0
- package/dist/queues/queue-manager.js.map +1 -0
- package/dist/sockets/index.d.ts +2 -0
- package/dist/sockets/index.js +3 -0
- package/dist/sockets/index.js.map +1 -0
- package/dist/sockets/load-sockets.d.ts +13 -0
- package/dist/sockets/load-sockets.js +42 -0
- package/dist/sockets/load-sockets.js.map +1 -0
- package/dist/sockets/socket-manager.d.ts +39 -0
- package/dist/sockets/socket-manager.js +85 -0
- package/dist/sockets/socket-manager.js.map +1 -0
- package/package.json +65 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Schema for one entry in a module's `meta.variables.json`.
|
|
4
|
+
*
|
|
5
|
+
* The file is deliberately plain JSON (not TS): tooling — `.env.example`
|
|
6
|
+
* generation, docs, the postman generator — must be able to read a module's
|
|
7
|
+
* env contract without executing its code.
|
|
8
|
+
*/
|
|
9
|
+
export declare const variableTypes: readonly ["string", "number", "boolean", "enum", "url",
|
|
10
|
+
/** Same as string, but redacted in logs, reports and snapshots. */
|
|
11
|
+
"secret"];
|
|
12
|
+
export type VariableType = (typeof variableTypes)[number];
|
|
13
|
+
/** Values a variable can resolve to after coercion. */
|
|
14
|
+
export type VariableValue = string | number | boolean;
|
|
15
|
+
export declare const variableSpecSchema: z.ZodEffects<z.ZodObject<{
|
|
16
|
+
name: z.ZodString;
|
|
17
|
+
type: z.ZodEnum<["string", "number", "boolean", "enum", "url", "secret"]>;
|
|
18
|
+
description: z.ZodString;
|
|
19
|
+
/**
|
|
20
|
+
* Free-form label tying related variables together (e.g. every Mongo
|
|
21
|
+
* credential under "database") — drives section grouping in generated
|
|
22
|
+
* .env.example / docs. Purely organizational, hence optional.
|
|
23
|
+
*/
|
|
24
|
+
group: z.ZodOptional<z.ZodString>;
|
|
25
|
+
enum: z.ZodOptional<z.ZodArray<z.ZodString, "atleastone">>;
|
|
26
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
27
|
+
default: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
28
|
+
}, "strip", z.ZodTypeAny, {
|
|
29
|
+
name: string;
|
|
30
|
+
type: "boolean" | "enum" | "number" | "secret" | "string" | "url";
|
|
31
|
+
description: string;
|
|
32
|
+
group?: string | undefined;
|
|
33
|
+
enum?: [string, ...string[]] | undefined;
|
|
34
|
+
required?: boolean | undefined;
|
|
35
|
+
default?: string | number | boolean | undefined;
|
|
36
|
+
}, {
|
|
37
|
+
name: string;
|
|
38
|
+
type: "boolean" | "enum" | "number" | "secret" | "string" | "url";
|
|
39
|
+
description: string;
|
|
40
|
+
group?: string | undefined;
|
|
41
|
+
enum?: [string, ...string[]] | undefined;
|
|
42
|
+
required?: boolean | undefined;
|
|
43
|
+
default?: string | number | boolean | undefined;
|
|
44
|
+
}>, {
|
|
45
|
+
name: string;
|
|
46
|
+
type: "boolean" | "enum" | "number" | "secret" | "string" | "url";
|
|
47
|
+
description: string;
|
|
48
|
+
group?: string | undefined;
|
|
49
|
+
enum?: [string, ...string[]] | undefined;
|
|
50
|
+
required?: boolean | undefined;
|
|
51
|
+
default?: string | number | boolean | undefined;
|
|
52
|
+
}, {
|
|
53
|
+
name: string;
|
|
54
|
+
type: "boolean" | "enum" | "number" | "secret" | "string" | "url";
|
|
55
|
+
description: string;
|
|
56
|
+
group?: string | undefined;
|
|
57
|
+
enum?: [string, ...string[]] | undefined;
|
|
58
|
+
required?: boolean | undefined;
|
|
59
|
+
default?: string | number | boolean | undefined;
|
|
60
|
+
}>;
|
|
61
|
+
export type VariableSpec = z.infer<typeof variableSpecSchema>;
|
|
62
|
+
export declare const metaFileSchema: z.ZodObject<{
|
|
63
|
+
$schema: z.ZodOptional<z.ZodString>;
|
|
64
|
+
variables: z.ZodArray<z.ZodEffects<z.ZodObject<{
|
|
65
|
+
name: z.ZodString;
|
|
66
|
+
type: z.ZodEnum<["string", "number", "boolean", "enum", "url", "secret"]>;
|
|
67
|
+
description: z.ZodString;
|
|
68
|
+
/**
|
|
69
|
+
* Free-form label tying related variables together (e.g. every Mongo
|
|
70
|
+
* credential under "database") — drives section grouping in generated
|
|
71
|
+
* .env.example / docs. Purely organizational, hence optional.
|
|
72
|
+
*/
|
|
73
|
+
group: z.ZodOptional<z.ZodString>;
|
|
74
|
+
enum: z.ZodOptional<z.ZodArray<z.ZodString, "atleastone">>;
|
|
75
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
76
|
+
default: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
77
|
+
}, "strip", z.ZodTypeAny, {
|
|
78
|
+
name: string;
|
|
79
|
+
type: "boolean" | "enum" | "number" | "secret" | "string" | "url";
|
|
80
|
+
description: string;
|
|
81
|
+
group?: string | undefined;
|
|
82
|
+
enum?: [string, ...string[]] | undefined;
|
|
83
|
+
required?: boolean | undefined;
|
|
84
|
+
default?: string | number | boolean | undefined;
|
|
85
|
+
}, {
|
|
86
|
+
name: string;
|
|
87
|
+
type: "boolean" | "enum" | "number" | "secret" | "string" | "url";
|
|
88
|
+
description: string;
|
|
89
|
+
group?: string | undefined;
|
|
90
|
+
enum?: [string, ...string[]] | undefined;
|
|
91
|
+
required?: boolean | undefined;
|
|
92
|
+
default?: string | number | boolean | undefined;
|
|
93
|
+
}>, {
|
|
94
|
+
name: string;
|
|
95
|
+
type: "boolean" | "enum" | "number" | "secret" | "string" | "url";
|
|
96
|
+
description: string;
|
|
97
|
+
group?: string | undefined;
|
|
98
|
+
enum?: [string, ...string[]] | undefined;
|
|
99
|
+
required?: boolean | undefined;
|
|
100
|
+
default?: string | number | boolean | undefined;
|
|
101
|
+
}, {
|
|
102
|
+
name: string;
|
|
103
|
+
type: "boolean" | "enum" | "number" | "secret" | "string" | "url";
|
|
104
|
+
description: string;
|
|
105
|
+
group?: string | undefined;
|
|
106
|
+
enum?: [string, ...string[]] | undefined;
|
|
107
|
+
required?: boolean | undefined;
|
|
108
|
+
default?: string | number | boolean | undefined;
|
|
109
|
+
}>, "many">;
|
|
110
|
+
}, "strip", z.ZodTypeAny, {
|
|
111
|
+
$schema?: string | undefined;
|
|
112
|
+
variables: {
|
|
113
|
+
name: string;
|
|
114
|
+
type: "boolean" | "enum" | "number" | "secret" | "string" | "url";
|
|
115
|
+
description: string;
|
|
116
|
+
group?: string | undefined;
|
|
117
|
+
enum?: [string, ...string[]] | undefined;
|
|
118
|
+
required?: boolean | undefined;
|
|
119
|
+
default?: string | number | boolean | undefined;
|
|
120
|
+
}[];
|
|
121
|
+
}, {
|
|
122
|
+
$schema?: string | undefined;
|
|
123
|
+
variables: {
|
|
124
|
+
name: string;
|
|
125
|
+
type: "boolean" | "enum" | "number" | "secret" | "string" | "url";
|
|
126
|
+
description: string;
|
|
127
|
+
group?: string | undefined;
|
|
128
|
+
enum?: [string, ...string[]] | undefined;
|
|
129
|
+
required?: boolean | undefined;
|
|
130
|
+
default?: string | number | boolean | undefined;
|
|
131
|
+
}[];
|
|
132
|
+
}>;
|
|
133
|
+
export type MetaFile = z.infer<typeof metaFileSchema>;
|
package/dist/env/spec.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Schema for one entry in a module's `meta.variables.json`.
|
|
4
|
+
*
|
|
5
|
+
* The file is deliberately plain JSON (not TS): tooling — `.env.example`
|
|
6
|
+
* generation, docs, the postman generator — must be able to read a module's
|
|
7
|
+
* env contract without executing its code.
|
|
8
|
+
*/
|
|
9
|
+
export const variableTypes = [
|
|
10
|
+
"string",
|
|
11
|
+
"number",
|
|
12
|
+
"boolean",
|
|
13
|
+
"enum",
|
|
14
|
+
"url",
|
|
15
|
+
/** Same as string, but redacted in logs, reports and snapshots. */
|
|
16
|
+
"secret",
|
|
17
|
+
];
|
|
18
|
+
const VARIABLE_NAME = /^[A-Z][A-Z0-9_]*$/;
|
|
19
|
+
export const variableSpecSchema = z
|
|
20
|
+
.object({
|
|
21
|
+
name: z
|
|
22
|
+
.string()
|
|
23
|
+
.regex(VARIABLE_NAME, "must be UPPER_SNAKE_CASE (e.g. USERS_MAX_SESSIONS)"),
|
|
24
|
+
type: z.enum(variableTypes),
|
|
25
|
+
description: z.string().min(1),
|
|
26
|
+
/**
|
|
27
|
+
* Free-form label tying related variables together (e.g. every Mongo
|
|
28
|
+
* credential under "database") — drives section grouping in generated
|
|
29
|
+
* .env.example / docs. Purely organizational, hence optional.
|
|
30
|
+
*/
|
|
31
|
+
group: z.string().min(1).optional(),
|
|
32
|
+
enum: z.array(z.string()).nonempty().optional(),
|
|
33
|
+
required: z.boolean().optional(),
|
|
34
|
+
default: z.union([z.string(), z.number(), z.boolean()]).optional(),
|
|
35
|
+
})
|
|
36
|
+
.superRefine((spec, ctx) => {
|
|
37
|
+
// These pairings can't be expressed structurally, so they live here:
|
|
38
|
+
if (spec.type === "enum" && !spec.enum) {
|
|
39
|
+
ctx.addIssue({
|
|
40
|
+
code: z.ZodIssueCode.custom,
|
|
41
|
+
message: `"${spec.name}": type "enum" requires an "enum" value list`,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
if (spec.type !== "enum" && spec.enum) {
|
|
45
|
+
ctx.addIssue({
|
|
46
|
+
code: z.ZodIssueCode.custom,
|
|
47
|
+
message: `"${spec.name}": "enum" list is only valid with type "enum"`,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
// required means "the deployment must supply it" — a default would
|
|
51
|
+
// silently defeat that, so the combination is rejected outright.
|
|
52
|
+
if (spec.required && spec.default !== undefined) {
|
|
53
|
+
ctx.addIssue({
|
|
54
|
+
code: z.ZodIssueCode.custom,
|
|
55
|
+
message: `"${spec.name}": "required" and "default" are mutually exclusive`,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
export const metaFileSchema = z.object({
|
|
60
|
+
$schema: z.string().optional(),
|
|
61
|
+
variables: z.array(variableSpecSchema),
|
|
62
|
+
});
|
|
63
|
+
//# sourceMappingURL=spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spec.js","sourceRoot":"","sources":["../../src/env/spec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,QAAQ;IACR,QAAQ;IACR,SAAS;IACT,MAAM;IACN,KAAK;IACL,mEAAmE;IACnE,QAAQ;CACA,CAAC;AAOX,MAAM,aAAa,GAAG,mBAAmB,CAAC;AAE1C,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC;KAChC,MAAM,CAAC;IACN,IAAI,EAAE,CAAC;SACJ,MAAM,EAAE;SACR,KAAK,CAAC,aAAa,EAAE,oDAAoD,CAAC;IAC7E,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC;IAC3B,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9B;;;;OAIG;IACH,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IACnC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAC/C,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAChC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE;CACnE,CAAC;KACD,WAAW,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;IACzB,qEAAqE;IACrE,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACvC,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,CAAC,CAAC,YAAY,CAAC,MAAM;YAC3B,OAAO,EAAE,IAAI,IAAI,CAAC,IAAI,8CAA8C;SACrE,CAAC,CAAC;IACL,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACtC,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,CAAC,CAAC,YAAY,CAAC,MAAM;YAC3B,OAAO,EAAE,IAAI,IAAI,CAAC,IAAI,+CAA+C;SACtE,CAAC,CAAC;IACL,CAAC;IACD,mEAAmE;IACnE,iEAAiE;IACjE,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QAChD,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,CAAC,CAAC,YAAY,CAAC,MAAM;YAC3B,OAAO,EAAE,IAAI,IAAI,CAAC,IAAI,oDAAoD;SAC3E,CAAC,CAAC;IACL,CAAC;AACH,CAAC,CAAC,CAAC;AAIL,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC9B,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,kBAAkB,CAAC;CACvC,CAAC,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { VariableSpec, VariableValue } from "./spec.js";
|
|
2
|
+
/**
|
|
3
|
+
* The resolved, validated variable store the rest of the framework reads from.
|
|
4
|
+
*
|
|
5
|
+
* Fail-closed on the read side too: asking for a variable no module declared
|
|
6
|
+
* throws immediately instead of returning undefined — an undeclared read is
|
|
7
|
+
* always a bug (either a typo, or a missing meta.variables.json entry), and
|
|
8
|
+
* surfacing it at the call site beats debugging an undefined downstream.
|
|
9
|
+
*/
|
|
10
|
+
export declare class VariableStore {
|
|
11
|
+
#private;
|
|
12
|
+
/** @internal Populated by the Environment engine during boot. */
|
|
13
|
+
declare_(spec: VariableSpec, owner: string, value: VariableValue): void;
|
|
14
|
+
has(name: string): boolean;
|
|
15
|
+
get<T extends VariableValue = VariableValue>(name: string): T;
|
|
16
|
+
/** Runtime override — primarily for tests. The variable must be declared. */
|
|
17
|
+
set(name: string, value: VariableValue): void;
|
|
18
|
+
ownerOf(name: string): string | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Flat view for logging / debugging. Secrets are redacted by default and
|
|
21
|
+
* opting out is deliberately loud in the caller's code.
|
|
22
|
+
*/
|
|
23
|
+
snapshot(options?: {
|
|
24
|
+
revealSecrets?: boolean;
|
|
25
|
+
}): Record<string, VariableValue>;
|
|
26
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The resolved, validated variable store the rest of the framework reads from.
|
|
3
|
+
*
|
|
4
|
+
* Fail-closed on the read side too: asking for a variable no module declared
|
|
5
|
+
* throws immediately instead of returning undefined — an undeclared read is
|
|
6
|
+
* always a bug (either a typo, or a missing meta.variables.json entry), and
|
|
7
|
+
* surfacing it at the call site beats debugging an undefined downstream.
|
|
8
|
+
*/
|
|
9
|
+
export class VariableStore {
|
|
10
|
+
#variables = new Map();
|
|
11
|
+
/** @internal Populated by the Environment engine during boot. */
|
|
12
|
+
declare_(spec, owner, value) {
|
|
13
|
+
this.#variables.set(spec.name, { spec, owner, value });
|
|
14
|
+
}
|
|
15
|
+
has(name) {
|
|
16
|
+
return this.#variables.has(name);
|
|
17
|
+
}
|
|
18
|
+
get(name) {
|
|
19
|
+
const stored = this.#variables.get(name);
|
|
20
|
+
if (!stored) {
|
|
21
|
+
throw new Error(`Environment variable "${name}" was never declared by any module's meta.variables.json`);
|
|
22
|
+
}
|
|
23
|
+
return stored.value;
|
|
24
|
+
}
|
|
25
|
+
/** Runtime override — primarily for tests. The variable must be declared. */
|
|
26
|
+
set(name, value) {
|
|
27
|
+
const stored = this.#variables.get(name);
|
|
28
|
+
if (!stored) {
|
|
29
|
+
throw new Error(`Cannot set undeclared environment variable "${name}"`);
|
|
30
|
+
}
|
|
31
|
+
stored.value = value;
|
|
32
|
+
}
|
|
33
|
+
ownerOf(name) {
|
|
34
|
+
return this.#variables.get(name)?.owner;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Flat view for logging / debugging. Secrets are redacted by default and
|
|
38
|
+
* opting out is deliberately loud in the caller's code.
|
|
39
|
+
*/
|
|
40
|
+
snapshot(options = {}) {
|
|
41
|
+
const view = {};
|
|
42
|
+
for (const [name, { spec, value }] of this.#variables) {
|
|
43
|
+
view[name] =
|
|
44
|
+
spec.type === "secret" && !options.revealSecrets ? "•••" : value;
|
|
45
|
+
}
|
|
46
|
+
return view;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=variable-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"variable-store.js","sourceRoot":"","sources":["../../src/env/variable-store.ts"],"names":[],"mappings":"AASA;;;;;;;GAOG;AACH,MAAM,OAAO,aAAa;IACf,UAAU,GAAG,IAAI,GAAG,EAA0B,CAAC;IAExD,iEAAiE;IACjE,QAAQ,CAAC,IAAkB,EAAE,KAAa,EAAE,KAAoB;QAC9D,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IACzD,CAAC;IAED,GAAG,CAAC,IAAY;QACd,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,GAAG,CAA0C,IAAY;QACvD,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CACb,yBAAyB,IAAI,0DAA0D,CACxF,CAAC;QACJ,CAAC;QACD,OAAO,MAAM,CAAC,KAAU,CAAC;IAC3B,CAAC;IAED,6EAA6E;IAC7E,GAAG,CAAC,IAAY,EAAE,KAAoB;QACpC,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,+CAA+C,IAAI,GAAG,CAAC,CAAC;QAC1E,CAAC;QACD,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;IACvB,CAAC;IAED,OAAO,CAAC,IAAY;QAClB,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC;IAC1C,CAAC;IAED;;;OAGG;IACH,QAAQ,CAAC,OAAO,GAAgC,EAAE;QAChD,MAAM,IAAI,GAAkC,EAAE,CAAC;QAC/C,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACtD,IAAI,CAAC,IAAI,CAAC;gBACR,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;QACrE,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tulipes fails closed at boot: a phase never crashes on the first problem it
|
|
3
|
+
* meets. It collects everything wrong, then throws once with the full list —
|
|
4
|
+
* so a developer with ten missing env variables fixes ten, not one per restart.
|
|
5
|
+
*/
|
|
6
|
+
export type BootScope = "env" | "modules" | "config" | "acl" | "boot";
|
|
7
|
+
export interface BootIssue {
|
|
8
|
+
scope: BootScope;
|
|
9
|
+
/** Module short-name, when the issue is attributable to a single module. */
|
|
10
|
+
module?: string;
|
|
11
|
+
message: string;
|
|
12
|
+
}
|
|
13
|
+
export declare class TulipesBootError extends Error {
|
|
14
|
+
#private;
|
|
15
|
+
readonly phase: string;
|
|
16
|
+
readonly issues: readonly BootIssue[];
|
|
17
|
+
constructor(phase: string, issues: readonly BootIssue[]);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Shared collector passed through a boot phase. Engines `add()` freely;
|
|
21
|
+
* the phase driver calls `throwIfAny()` at its checkpoint.
|
|
22
|
+
*/
|
|
23
|
+
export declare class BootReport {
|
|
24
|
+
#private;
|
|
25
|
+
add(issue: BootIssue): void;
|
|
26
|
+
get issues(): readonly BootIssue[];
|
|
27
|
+
throwIfAny(phase: string): void;
|
|
28
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tulipes fails closed at boot: a phase never crashes on the first problem it
|
|
3
|
+
* meets. It collects everything wrong, then throws once with the full list —
|
|
4
|
+
* so a developer with ten missing env variables fixes ten, not one per restart.
|
|
5
|
+
*/
|
|
6
|
+
export class TulipesBootError extends Error {
|
|
7
|
+
phase;
|
|
8
|
+
issues;
|
|
9
|
+
constructor(phase, issues) {
|
|
10
|
+
super(TulipesBootError.#format(phase, issues));
|
|
11
|
+
this.name = "TulipesBootError";
|
|
12
|
+
this.phase = phase;
|
|
13
|
+
this.issues = issues;
|
|
14
|
+
}
|
|
15
|
+
static #format(phase, issues) {
|
|
16
|
+
const lines = issues.map((issue) => {
|
|
17
|
+
const origin = issue.module ? `[${issue.module}] ` : "";
|
|
18
|
+
return ` ✖ ${origin}${issue.message}`;
|
|
19
|
+
});
|
|
20
|
+
return [
|
|
21
|
+
`Boot aborted during "${phase}" — ${issues.length} issue(s):`,
|
|
22
|
+
...lines,
|
|
23
|
+
].join("\n");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Shared collector passed through a boot phase. Engines `add()` freely;
|
|
28
|
+
* the phase driver calls `throwIfAny()` at its checkpoint.
|
|
29
|
+
*/
|
|
30
|
+
export class BootReport {
|
|
31
|
+
#issues = [];
|
|
32
|
+
add(issue) {
|
|
33
|
+
this.#issues.push(issue);
|
|
34
|
+
}
|
|
35
|
+
get issues() {
|
|
36
|
+
return this.#issues;
|
|
37
|
+
}
|
|
38
|
+
throwIfAny(phase) {
|
|
39
|
+
if (this.#issues.length > 0) {
|
|
40
|
+
throw new TulipesBootError(phase, this.#issues);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=boot-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boot-error.js","sourceRoot":"","sources":["../../src/errors/boot-error.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAChC,KAAK,CAAS;IACd,MAAM,CAAuB;IAEtC,YAAY,KAAa,EAAE,MAA4B;QACrD,KAAK,CAAC,gBAAgB,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;QAC/C,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;QAC/B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,MAAM,CAAC,OAAO,CAAC,KAAa,EAAE,MAA4B;QACxD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YACjC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACxD,OAAO,OAAO,MAAM,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;QACzC,CAAC,CAAC,CAAC;QACH,OAAO;YACL,wBAAwB,KAAK,OAAO,MAAM,CAAC,MAAM,YAAY;YAC7D,GAAG,KAAK;SACT,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,OAAO,UAAU;IACZ,OAAO,GAAgB,EAAE,CAAC;IAEnC,GAAG,CAAC,KAAgB;QAClB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,UAAU,CAAC,KAAa;QACtB,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,gBAAgB,CAAC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { BootReport, TulipesBootError, type BootIssue, type BootScope, } from "./boot-error.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,gBAAgB,GAGjB,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ErrorRequestHandler, RequestHandler } from "express";
|
|
2
|
+
import type { AppEnv } from "../env/environment.js";
|
|
3
|
+
/**
|
|
4
|
+
* The error a route throws on purpose. Anything else reaching the error
|
|
5
|
+
* handler is a bug and gets the anonymous-500 treatment.
|
|
6
|
+
*
|
|
7
|
+
* Express 5 forwards rejected async handlers to the error middleware
|
|
8
|
+
* automatically, so `throw new HttpError(404, "no such user")` inside an
|
|
9
|
+
* async route is the entire error-handling story a module needs.
|
|
10
|
+
*/
|
|
11
|
+
export declare class HttpError extends Error {
|
|
12
|
+
readonly status: number;
|
|
13
|
+
readonly details?: unknown;
|
|
14
|
+
constructor(status: number, message: string, details?: unknown);
|
|
15
|
+
}
|
|
16
|
+
/** Terminal 404 — anything no router claimed. */
|
|
17
|
+
export declare function notFoundHandler(): RequestHandler;
|
|
18
|
+
/**
|
|
19
|
+
* The single, final error middleware. Fail-closed on information leaks:
|
|
20
|
+
* unexpected errors expose no message, no stack, nothing — except in
|
|
21
|
+
* development, where hiding them only slows the developer down.
|
|
22
|
+
*/
|
|
23
|
+
export declare function errorHandler(appEnv: AppEnv): ErrorRequestHandler;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The error a route throws on purpose. Anything else reaching the error
|
|
3
|
+
* handler is a bug and gets the anonymous-500 treatment.
|
|
4
|
+
*
|
|
5
|
+
* Express 5 forwards rejected async handlers to the error middleware
|
|
6
|
+
* automatically, so `throw new HttpError(404, "no such user")` inside an
|
|
7
|
+
* async route is the entire error-handling story a module needs.
|
|
8
|
+
*/
|
|
9
|
+
export class HttpError extends Error {
|
|
10
|
+
status;
|
|
11
|
+
details;
|
|
12
|
+
constructor(status, message, details) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = "HttpError";
|
|
15
|
+
this.status = status;
|
|
16
|
+
this.details = details;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Terminal 404 — anything no router claimed. */
|
|
20
|
+
export function notFoundHandler() {
|
|
21
|
+
return (req, _res, next) => {
|
|
22
|
+
next(new HttpError(404, `no route for ${req.method} ${req.path}`));
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The single, final error middleware. Fail-closed on information leaks:
|
|
27
|
+
* unexpected errors expose no message, no stack, nothing — except in
|
|
28
|
+
* development, where hiding them only slows the developer down.
|
|
29
|
+
*/
|
|
30
|
+
export function errorHandler(appEnv) {
|
|
31
|
+
const revealInternals = appEnv === "development" || appEnv === "test";
|
|
32
|
+
return (error, _req, res, _next) => {
|
|
33
|
+
if (error instanceof HttpError) {
|
|
34
|
+
res.status(error.status).json({
|
|
35
|
+
error: error.message,
|
|
36
|
+
...(error.details !== undefined && { details: error.details }),
|
|
37
|
+
});
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
console.error("[http] unhandled error:", error);
|
|
41
|
+
res.status(500).json({
|
|
42
|
+
error: revealInternals
|
|
43
|
+
? `internal: ${error?.message ?? String(error)}`
|
|
44
|
+
: "internal server error",
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=error-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-handler.js","sourceRoot":"","sources":["../../src/http/error-handler.ts"],"names":[],"mappings":"AAIA;;;;;;;GAOG;AACH,MAAM,OAAO,SAAU,SAAQ,KAAK;IACzB,MAAM,CAAS;IACf,OAAO,CAAW;IAE3B,YAAY,MAAc,EAAE,OAAe,EAAE,OAAiB;QAC5D,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;QACxB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AAED,iDAAiD;AACjD,MAAM,UAAU,eAAe;IAC7B,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;QACzB,IAAI,CAAC,IAAI,SAAS,CAAC,GAAG,EAAE,gBAAgB,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACrE,CAAC,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,MAAM,eAAe,GAAG,MAAM,KAAK,aAAa,IAAI,MAAM,KAAK,MAAM,CAAC;IAEtE,OAAO,CAAC,KAAc,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;QAC1C,IAAI,KAAK,YAAY,SAAS,EAAE,CAAC;YAC/B,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC;gBAC5B,KAAK,EAAE,KAAK,CAAC,OAAO;gBACpB,GAAG,CAAC,KAAK,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;aAC/D,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,KAAK,CAAC,CAAC;QAChD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YACnB,KAAK,EAAE,eAAe;gBACpB,CAAC,CAAC,aAAc,KAAe,EAAE,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE;gBAC3D,CAAC,CAAC,uBAAuB;SAC5B,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { HttpError, errorHandler, notFoundHandler } from "./error-handler.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/http/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from "./acl/index.js";
|
|
2
|
+
export * from "./boot/index.js";
|
|
3
|
+
export * from "./config/index.js";
|
|
4
|
+
export * from "./db/index.js";
|
|
5
|
+
export * from "./env/index.js";
|
|
6
|
+
export * from "./http/index.js";
|
|
7
|
+
export * from "./modules/index.js";
|
|
8
|
+
export * from "./queues/index.js";
|
|
9
|
+
export * from "./sockets/index.js";
|
|
10
|
+
export * from "./errors/index.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from "./acl/index.js";
|
|
2
|
+
export * from "./boot/index.js";
|
|
3
|
+
export * from "./config/index.js";
|
|
4
|
+
export * from "./db/index.js";
|
|
5
|
+
export * from "./env/index.js";
|
|
6
|
+
export * from "./http/index.js";
|
|
7
|
+
export * from "./modules/index.js";
|
|
8
|
+
export * from "./queues/index.js";
|
|
9
|
+
export * from "./sockets/index.js";
|
|
10
|
+
export * from "./errors/index.js";
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BootReport } from "../errors/boot-error.js";
|
|
2
|
+
import { type ModuleManifest } from "./manifest.js";
|
|
3
|
+
/**
|
|
4
|
+
* Read each module's package.json (under `modules/<name>`) into a manifest.
|
|
5
|
+
* Modules are yarn
|
|
6
|
+
* workspace packages, so a folder without a package.json is a broken module,
|
|
7
|
+
* not an ignorable one. As everywhere at boot: problems are collected into
|
|
8
|
+
* the report, never thrown one-by-one.
|
|
9
|
+
*/
|
|
10
|
+
export declare function discoverModules(modulesDir: string, report: BootReport): ModuleManifest[];
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { BootReport } from "../errors/boot-error.js";
|
|
4
|
+
import { tulipesManifestSchema } from "./manifest.js";
|
|
5
|
+
/**
|
|
6
|
+
* Read each module's package.json (under `modules/<name>`) into a manifest.
|
|
7
|
+
* Modules are yarn
|
|
8
|
+
* workspace packages, so a folder without a package.json is a broken module,
|
|
9
|
+
* not an ignorable one. As everywhere at boot: problems are collected into
|
|
10
|
+
* the report, never thrown one-by-one.
|
|
11
|
+
*/
|
|
12
|
+
export function discoverModules(modulesDir, report) {
|
|
13
|
+
let entries;
|
|
14
|
+
try {
|
|
15
|
+
entries = readdirSync(modulesDir, { withFileTypes: true });
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
report.add({
|
|
19
|
+
scope: "modules",
|
|
20
|
+
message: `modules directory not found: ${modulesDir}`,
|
|
21
|
+
});
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
const manifests = [];
|
|
25
|
+
const seen = new Map(); // short name → package name
|
|
26
|
+
for (const entry of entries) {
|
|
27
|
+
if (!entry.isDirectory() || entry.name.startsWith("."))
|
|
28
|
+
continue;
|
|
29
|
+
const dir = join(modulesDir, entry.name);
|
|
30
|
+
const manifest = readManifest(dir, entry.name, report);
|
|
31
|
+
if (!manifest)
|
|
32
|
+
continue;
|
|
33
|
+
// Short names must be unique: they are the dependsOn address space.
|
|
34
|
+
// Two scopes owning the same segment (@a/users, @b/users) would make
|
|
35
|
+
// "dependsOn: [users]" ambiguous.
|
|
36
|
+
const clash = seen.get(manifest.name);
|
|
37
|
+
if (clash) {
|
|
38
|
+
report.add({
|
|
39
|
+
scope: "modules",
|
|
40
|
+
module: manifest.name,
|
|
41
|
+
message: `short name "${manifest.name}" is claimed by both "${clash}" and "${manifest.packageName}"`,
|
|
42
|
+
});
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
seen.set(manifest.name, manifest.packageName);
|
|
46
|
+
manifests.push(manifest);
|
|
47
|
+
}
|
|
48
|
+
return manifests;
|
|
49
|
+
}
|
|
50
|
+
function readManifest(dir, folder, report) {
|
|
51
|
+
let json;
|
|
52
|
+
try {
|
|
53
|
+
json = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
report.add({
|
|
57
|
+
scope: "modules",
|
|
58
|
+
module: folder,
|
|
59
|
+
message: `package.json missing or invalid: ${error.message}`,
|
|
60
|
+
});
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
if (typeof json.name !== "string" || json.name.length === 0) {
|
|
64
|
+
report.add({
|
|
65
|
+
scope: "modules",
|
|
66
|
+
module: folder,
|
|
67
|
+
message: `package.json has no "name" — modules must be workspace packages`,
|
|
68
|
+
});
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
// Absent "tulipes" key is a valid module with default ordering.
|
|
72
|
+
const parsed = tulipesManifestSchema.safeParse(json.tulipes ?? {});
|
|
73
|
+
if (!parsed.success) {
|
|
74
|
+
for (const issue of parsed.error.issues) {
|
|
75
|
+
report.add({
|
|
76
|
+
scope: "modules",
|
|
77
|
+
module: folder,
|
|
78
|
+
message: `package.json "tulipes" key: ${issue.path.join(".") || "(root)"} — ${issue.message}`,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
const shortName = json.name.split("/").at(-1);
|
|
84
|
+
return {
|
|
85
|
+
name: shortName,
|
|
86
|
+
packageName: json.name,
|
|
87
|
+
dir,
|
|
88
|
+
...parsed.data,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/modules/discovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ModuleManifest } from "./manifest.js";
|
|
2
|
+
/**
|
|
3
|
+
* Boot phase 5: given the sorted module list, resolve every conventional file
|
|
4
|
+
* the loaders will consume — paths only, nothing is imported yet. Importing
|
|
5
|
+
* happens later, phase by phase, so a broken routes file can't take down the
|
|
6
|
+
* environment report, and models load before routes exactly as the pipeline
|
|
7
|
+
* dictates.
|
|
8
|
+
*
|
|
9
|
+
* Missing directories and files are simply features a module doesn't use —
|
|
10
|
+
* never an error. The explorer has no failure modes by design.
|
|
11
|
+
*/
|
|
12
|
+
export type SourceExtension = ".ts" | ".js";
|
|
13
|
+
/**
|
|
14
|
+
* In dev the app runs straight from TypeScript (via tsx) so conventions
|
|
15
|
+
* resolve to .ts files; a compiled deployment runs .js from dist. Core infers
|
|
16
|
+
* which world it's in from its own file — this very file is only ever .js
|
|
17
|
+
* after a build. The URL alone isn't trustworthy: tsx's tsImport presents
|
|
18
|
+
* modules under a .js URL while only the .ts source exists on disk, so a
|
|
19
|
+
* .js verdict is confirmed against the filesystem.
|
|
20
|
+
*/
|
|
21
|
+
export declare function detectSourceExtension(): SourceExtension;
|
|
22
|
+
export interface ModuleFiles {
|
|
23
|
+
module: ModuleManifest;
|
|
24
|
+
/** `module.config.<ext>` — config factory + lifecycle hooks. */
|
|
25
|
+
configFile?: string;
|
|
26
|
+
/** `module.acl.<ext>` — permission grants for this module's resources. */
|
|
27
|
+
aclFile?: string;
|
|
28
|
+
routeFiles: readonly string[];
|
|
29
|
+
modelFiles: readonly string[];
|
|
30
|
+
bootstrapFiles: readonly string[];
|
|
31
|
+
queueFiles: readonly string[];
|
|
32
|
+
socketFiles: readonly string[];
|
|
33
|
+
}
|
|
34
|
+
export interface ExploreOptions {
|
|
35
|
+
/** Override the runtime detection — used by tooling and tests. */
|
|
36
|
+
sourceExtension?: SourceExtension;
|
|
37
|
+
}
|
|
38
|
+
export declare function exploreModules(modules: readonly ModuleManifest[], options?: ExploreOptions): ModuleFiles[];
|