crewly 1.20.133 → 1.20.134
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/backend/backend/src/constants.d.ts +82 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +87 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/bundle/bundle.controller.d.ts +44 -0
- package/dist/backend/backend/src/controllers/bundle/bundle.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/bundle/bundle.controller.js +122 -0
- package/dist/backend/backend/src/controllers/bundle/bundle.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/bundle/bundle.routes.d.ts +25 -0
- package/dist/backend/backend/src/controllers/bundle/bundle.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/bundle/bundle.routes.js +33 -0
- package/dist/backend/backend/src/controllers/bundle/bundle.routes.js.map +1 -0
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +14 -0
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.js +4 -0
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
- package/dist/backend/backend/src/services/bundle/bundle-apply.factory.d.ts +62 -0
- package/dist/backend/backend/src/services/bundle/bundle-apply.factory.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-apply.factory.js +173 -0
- package/dist/backend/backend/src/services/bundle/bundle-apply.factory.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-apply.service.d.ts +359 -0
- package/dist/backend/backend/src/services/bundle/bundle-apply.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-apply.service.js +888 -0
- package/dist/backend/backend/src/services/bundle/bundle-apply.service.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-catalog.d.ts +167 -0
- package/dist/backend/backend/src/services/bundle/bundle-catalog.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-catalog.js +248 -0
- package/dist/backend/backend/src/services/bundle/bundle-catalog.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-connectors.d.ts +43 -0
- package/dist/backend/backend/src/services/bundle/bundle-connectors.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-connectors.js +57 -0
- package/dist/backend/backend/src/services/bundle/bundle-connectors.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-deps.d.ts +37 -0
- package/dist/backend/backend/src/services/bundle/bundle-deps.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-deps.js +60 -0
- package/dist/backend/backend/src/services/bundle/bundle-deps.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-manifest.d.ts +93 -0
- package/dist/backend/backend/src/services/bundle/bundle-manifest.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-manifest.js +532 -0
- package/dist/backend/backend/src/services/bundle/bundle-manifest.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-placeholders.d.ts +100 -0
- package/dist/backend/backend/src/services/bundle/bundle-placeholders.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-placeholders.js +183 -0
- package/dist/backend/backend/src/services/bundle/bundle-placeholders.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-runtime.d.ts +36 -0
- package/dist/backend/backend/src/services/bundle/bundle-runtime.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-runtime.js +38 -0
- package/dist/backend/backend/src/services/bundle/bundle-runtime.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-skill-installer.d.ts +105 -0
- package/dist/backend/backend/src/services/bundle/bundle-skill-installer.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-skill-installer.js +144 -0
- package/dist/backend/backend/src/services/bundle/bundle-skill-installer.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-state.store.d.ts +48 -0
- package/dist/backend/backend/src/services/bundle/bundle-state.store.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-state.store.js +90 -0
- package/dist/backend/backend/src/services/bundle/bundle-state.store.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-team.builder.d.ts +81 -0
- package/dist/backend/backend/src/services/bundle/bundle-team.builder.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-team.builder.js +172 -0
- package/dist/backend/backend/src/services/bundle/bundle-team.builder.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-time.d.ts +59 -0
- package/dist/backend/backend/src/services/bundle/bundle-time.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-time.js +98 -0
- package/dist/backend/backend/src/services/bundle/bundle-time.js.map +1 -0
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js +5 -0
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js.map +1 -1
- package/dist/backend/backend/src/services/core/storage.service.js +1 -1
- package/dist/backend/backend/src/services/core/storage.service.js.map +1 -1
- package/dist/backend/backend/src/services/core/teams-backup.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/core/teams-backup.service.js +4 -2
- package/dist/backend/backend/src/services/core/teams-backup.service.js.map +1 -1
- package/dist/backend/backend/src/services/onboarding/onboarding-checklist.factory.d.ts.map +1 -1
- package/dist/backend/backend/src/services/onboarding/onboarding-checklist.factory.js +2 -0
- package/dist/backend/backend/src/services/onboarding/onboarding-checklist.factory.js.map +1 -1
- package/dist/backend/backend/src/services/onboarding/onboarding-checklist.service.d.ts +14 -2
- package/dist/backend/backend/src/services/onboarding/onboarding-checklist.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/onboarding/onboarding-checklist.service.js +37 -1
- package/dist/backend/backend/src/services/onboarding/onboarding-checklist.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts +21 -0
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.js +85 -0
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
- package/dist/backend/backend/src/services/template/template.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/template/template.service.js +5 -2
- package/dist/backend/backend/src/services/template/template.service.js.map +1 -1
- package/dist/backend/backend/src/services/workflow/cron-task.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/workflow/cron-task.service.js +4 -2
- package/dist/backend/backend/src/services/workflow/cron-task.service.js.map +1 -1
- package/dist/backend/backend/src/types/solution-bundle.types.d.ts +367 -0
- package/dist/backend/backend/src/types/solution-bundle.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/solution-bundle.types.js +17 -0
- package/dist/backend/backend/src/types/solution-bundle.types.js.map +1 -0
- package/dist/backend/backend/src/types/team-template.types.d.ts +8 -0
- package/dist/backend/backend/src/types/team-template.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/team-template.types.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +82 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +87 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/bundle/bundle-apply.service.d.ts +359 -0
- package/dist/cli/backend/src/services/bundle/bundle-apply.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-apply.service.js +888 -0
- package/dist/cli/backend/src/services/bundle/bundle-apply.service.js.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-catalog.d.ts +167 -0
- package/dist/cli/backend/src/services/bundle/bundle-catalog.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-catalog.js +248 -0
- package/dist/cli/backend/src/services/bundle/bundle-catalog.js.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-deps.d.ts +37 -0
- package/dist/cli/backend/src/services/bundle/bundle-deps.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-deps.js +60 -0
- package/dist/cli/backend/src/services/bundle/bundle-deps.js.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-manifest.d.ts +93 -0
- package/dist/cli/backend/src/services/bundle/bundle-manifest.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-manifest.js +532 -0
- package/dist/cli/backend/src/services/bundle/bundle-manifest.js.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-placeholders.d.ts +100 -0
- package/dist/cli/backend/src/services/bundle/bundle-placeholders.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-placeholders.js +183 -0
- package/dist/cli/backend/src/services/bundle/bundle-placeholders.js.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-runtime.d.ts +36 -0
- package/dist/cli/backend/src/services/bundle/bundle-runtime.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-runtime.js +38 -0
- package/dist/cli/backend/src/services/bundle/bundle-runtime.js.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-skill-installer.d.ts +105 -0
- package/dist/cli/backend/src/services/bundle/bundle-skill-installer.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-skill-installer.js +144 -0
- package/dist/cli/backend/src/services/bundle/bundle-skill-installer.js.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-state.store.d.ts +48 -0
- package/dist/cli/backend/src/services/bundle/bundle-state.store.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-state.store.js +90 -0
- package/dist/cli/backend/src/services/bundle/bundle-state.store.js.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-team.builder.d.ts +81 -0
- package/dist/cli/backend/src/services/bundle/bundle-team.builder.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-team.builder.js +172 -0
- package/dist/cli/backend/src/services/bundle/bundle-team.builder.js.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-time.d.ts +59 -0
- package/dist/cli/backend/src/services/bundle/bundle-time.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-time.js +98 -0
- package/dist/cli/backend/src/services/bundle/bundle-time.js.map +1 -0
- package/dist/cli/backend/src/services/core/storage.service.js +1 -1
- package/dist/cli/backend/src/services/core/storage.service.js.map +1 -1
- package/dist/cli/backend/src/services/core/teams-backup.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/core/teams-backup.service.js +4 -2
- package/dist/cli/backend/src/services/core/teams-backup.service.js.map +1 -1
- package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.d.ts +72 -0
- package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.js +389 -0
- package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.js.map +1 -0
- package/dist/cli/backend/src/services/messaging/index.d.ts +15 -0
- package/dist/cli/backend/src/services/messaging/index.d.ts.map +1 -0
- package/dist/cli/backend/src/services/messaging/index.js +20 -0
- package/dist/cli/backend/src/services/messaging/index.js.map +1 -0
- package/dist/cli/backend/src/services/messaging/queue-processor.service.d.ts +224 -0
- package/dist/cli/backend/src/services/messaging/queue-processor.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/messaging/queue-processor.service.js +969 -0
- package/dist/cli/backend/src/services/messaging/queue-processor.service.js.map +1 -0
- package/dist/cli/backend/src/services/messaging/response-router.service.d.ts +110 -0
- package/dist/cli/backend/src/services/messaging/response-router.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/messaging/response-router.service.js +268 -0
- package/dist/cli/backend/src/services/messaging/response-router.service.js.map +1 -0
- package/dist/cli/backend/src/services/onboarding/onboarding-checklist.service.d.ts +14 -2
- package/dist/cli/backend/src/services/onboarding/onboarding-checklist.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/onboarding/onboarding-checklist.service.js +37 -1
- package/dist/cli/backend/src/services/onboarding/onboarding-checklist.service.js.map +1 -1
- package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.d.ts +133 -0
- package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.d.ts.map +1 -0
- package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.js +232 -0
- package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.js.map +1 -0
- package/dist/cli/backend/src/services/skill/skill-catalog.service.d.ts +277 -0
- package/dist/cli/backend/src/services/skill/skill-catalog.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/skill/skill-catalog.service.js +708 -0
- package/dist/cli/backend/src/services/skill/skill-catalog.service.js.map +1 -0
- package/dist/cli/backend/src/services/skill-setup/skill-install-job.service.d.ts +243 -0
- package/dist/cli/backend/src/services/skill-setup/skill-install-job.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/skill-setup/skill-install-job.service.js +439 -0
- package/dist/cli/backend/src/services/skill-setup/skill-install-job.service.js.map +1 -0
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts +21 -0
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.js +85 -0
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
- package/dist/cli/backend/src/services/template/template.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/template/template.service.js +5 -2
- package/dist/cli/backend/src/services/template/template.service.js.map +1 -1
- package/dist/cli/backend/src/services/workflow/cron-task.service.d.ts +316 -0
- package/dist/cli/backend/src/services/workflow/cron-task.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/workflow/cron-task.service.js +1043 -0
- package/dist/cli/backend/src/services/workflow/cron-task.service.js.map +1 -0
- package/dist/cli/backend/src/types/cron-task.types.d.ts +95 -0
- package/dist/cli/backend/src/types/cron-task.types.d.ts.map +1 -0
- package/dist/cli/backend/src/types/cron-task.types.js +10 -0
- package/dist/cli/backend/src/types/cron-task.types.js.map +1 -0
- package/dist/cli/backend/src/types/solution-bundle.types.d.ts +367 -0
- package/dist/cli/backend/src/types/solution-bundle.types.d.ts.map +1 -0
- package/dist/cli/backend/src/types/solution-bundle.types.js +17 -0
- package/dist/cli/backend/src/types/solution-bundle.types.js.map +1 -0
- package/dist/cli/backend/src/types/team-template.types.d.ts +8 -0
- package/dist/cli/backend/src/types/team-template.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/team-template.types.js.map +1 -1
- package/dist/cli/cli/src/commands/deploy-bundle.d.ts +160 -0
- package/dist/cli/cli/src/commands/deploy-bundle.d.ts.map +1 -0
- package/dist/cli/cli/src/commands/deploy-bundle.js +386 -0
- package/dist/cli/cli/src/commands/deploy-bundle.js.map +1 -0
- package/dist/cli/cli/src/commands/onboard.d.ts +32 -1
- package/dist/cli/cli/src/commands/onboard.d.ts.map +1 -1
- package/dist/cli/cli/src/commands/onboard.js +80 -3
- package/dist/cli/cli/src/commands/onboard.js.map +1 -1
- package/dist/cli/cli/src/constants.d.ts +5 -0
- package/dist/cli/cli/src/constants.d.ts.map +1 -1
- package/dist/cli/cli/src/constants.js +5 -0
- package/dist/cli/cli/src/constants.js.map +1 -1
- package/dist/cli/cli/src/index.js +15 -0
- package/dist/cli/cli/src/index.js.map +1 -1
- package/frontend/dist/assets/{index-e6c403b6.js → index-97bb17d4.js} +274 -274
- package/frontend/dist/index.html +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Solution bundle manifest: validation and normalization.
|
|
3
|
+
*
|
|
4
|
+
* A bundle is an ordinary team template with a `bundle` section
|
|
5
|
+
* (types/solution-bundle.types.ts). This module checks everything the apply
|
|
6
|
+
* engine relies on, so a broken template is refused before anything is
|
|
7
|
+
* written: roles, a lead per team, member refs, placeholders, cron
|
|
8
|
+
* expressions, question shapes, connectors, file paths.
|
|
9
|
+
*
|
|
10
|
+
* @module services/bundle/bundle-manifest
|
|
11
|
+
*/
|
|
12
|
+
import { type TemplateRole } from '../../types/team-template.types.js';
|
|
13
|
+
import type { BundleTemplate } from '../../types/solution-bundle.types.js';
|
|
14
|
+
/** Result of validating a template's bundle section. */
|
|
15
|
+
export interface BundleValidation {
|
|
16
|
+
ok: boolean;
|
|
17
|
+
/** One line per problem, with a JSON-ish path */
|
|
18
|
+
errors: string[];
|
|
19
|
+
}
|
|
20
|
+
/** A team of the bundle, normalized: the main team plus `bundle.teams`. */
|
|
21
|
+
export interface NormalizedBundleTeam {
|
|
22
|
+
key: string;
|
|
23
|
+
/** Name before placeholders are filled */
|
|
24
|
+
name: string;
|
|
25
|
+
description: string;
|
|
26
|
+
roles: TemplateRole[];
|
|
27
|
+
/** Role id of the team's lead (hierarchyLevel 1 + canDelegate) */
|
|
28
|
+
leadRole: string;
|
|
29
|
+
}
|
|
30
|
+
/** A parsed member reference (`role` or `team/role`). */
|
|
31
|
+
export interface MemberRef {
|
|
32
|
+
teamKey: string;
|
|
33
|
+
role: string;
|
|
34
|
+
}
|
|
35
|
+
/** Optional file access, to check `file` references of norms / SOPs. */
|
|
36
|
+
export interface BundleValidationOptions {
|
|
37
|
+
/** Directory relative `file` paths resolve against */
|
|
38
|
+
dir?: string;
|
|
39
|
+
/** Read a file (defaults to none: file contents are not checked) */
|
|
40
|
+
readFile?: (absolutePath: string) => string | null;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Whether a raw template carries a `bundle` section.
|
|
44
|
+
*
|
|
45
|
+
* @param raw - Parsed template JSON
|
|
46
|
+
* @returns True when `bundle` is an object
|
|
47
|
+
*/
|
|
48
|
+
export declare function hasBundleSection(raw: unknown): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Check a five-field cron expression (`*`, numbers, ranges, lists, steps).
|
|
51
|
+
*
|
|
52
|
+
* @param expression - Cron expression
|
|
53
|
+
* @returns True when every field is valid and in range
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* isValidCronExpression('0 9 * * 1-5') // true
|
|
57
|
+
*/
|
|
58
|
+
export declare function isValidCronExpression(expression: string): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Parse a member reference.
|
|
61
|
+
*
|
|
62
|
+
* @param ref - `role` (main team) or `team/role`
|
|
63
|
+
* @returns The team key and role
|
|
64
|
+
*/
|
|
65
|
+
export declare function parseMemberRef(ref: string): MemberRef;
|
|
66
|
+
/**
|
|
67
|
+
* The lead role of a list of roles: the first with hierarchyLevel 1 that can
|
|
68
|
+
* delegate, else the first role.
|
|
69
|
+
*
|
|
70
|
+
* @param roles - Roles
|
|
71
|
+
* @returns Role id, or '' when there are no roles
|
|
72
|
+
*/
|
|
73
|
+
export declare function leadRoleOf(roles: TemplateRole[]): string;
|
|
74
|
+
/**
|
|
75
|
+
* Every team of a bundle: the template's own roles as the main team, then
|
|
76
|
+
* `bundle.teams`.
|
|
77
|
+
*
|
|
78
|
+
* @param template - A validated bundle template
|
|
79
|
+
* @returns Teams, main first
|
|
80
|
+
*/
|
|
81
|
+
export declare function bundleTeams(template: BundleTemplate): NormalizedBundleTeam[];
|
|
82
|
+
/**
|
|
83
|
+
* Validate a template that carries a `bundle` section.
|
|
84
|
+
*
|
|
85
|
+
* @param raw - Parsed template JSON
|
|
86
|
+
* @param options - File access for `file` references (optional)
|
|
87
|
+
* @returns `{ ok, errors }`
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* const { ok, errors } = validateBundleTemplate(JSON.parse(text), { dir, readFile });
|
|
91
|
+
*/
|
|
92
|
+
export declare function validateBundleTemplate(raw: unknown, options?: BundleValidationOptions): BundleValidation;
|
|
93
|
+
//# sourceMappingURL=bundle-manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle-manifest.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/bundle/bundle-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAC5F,OAAO,KAAK,EAGV,cAAc,EAEf,MAAM,sCAAsC,CAAC;AAG9C,wDAAwD;AACxD,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,OAAO,CAAC;IACZ,iDAAiD;IACjD,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,2EAA2E;AAC3E,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,yDAAyD;AACzD,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wEAAwE;AACxE,MAAM,WAAW,uBAAuB;IACtC,sDAAsD;IACtD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,QAAQ,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CACpD;AAeD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAEtD;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAgBjE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAIrD;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,MAAM,CAExD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,cAAc,GAAG,oBAAoB,EAAE,CAgB5E;AA2GD;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,GAAE,uBAA4B,GAAG,gBAAgB,CAgP5G"}
|
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Solution bundle manifest: validation and normalization.
|
|
3
|
+
*
|
|
4
|
+
* A bundle is an ordinary team template with a `bundle` section
|
|
5
|
+
* (types/solution-bundle.types.ts). This module checks everything the apply
|
|
6
|
+
* engine relies on, so a broken template is refused before anything is
|
|
7
|
+
* written: roles, a lead per team, member refs, placeholders, cron
|
|
8
|
+
* expressions, question shapes, connectors, file paths.
|
|
9
|
+
*
|
|
10
|
+
* @module services/bundle/bundle-manifest
|
|
11
|
+
*/
|
|
12
|
+
import * as path from 'path';
|
|
13
|
+
import { BUNDLE_CONSTANTS, RUNTIME_TYPES } from '../../constants.js';
|
|
14
|
+
import { isValidTemplateRole } from '../../types/team-template.types.js';
|
|
15
|
+
import { listPlaceholders } from './bundle-placeholders.js';
|
|
16
|
+
/** Kebab-case id (file names, keys). */
|
|
17
|
+
const SLUG_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
18
|
+
/** Question ids double as placeholder names. */
|
|
19
|
+
const QUESTION_ID_PATTERN = /^[a-z][a-z0-9_]*$/;
|
|
20
|
+
/** Session names are built from member names, so they stay ASCII. */
|
|
21
|
+
const ASCII_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9 _-]*$/;
|
|
22
|
+
/** `HH:MM`, 24-hour. */
|
|
23
|
+
const TIME_PATTERN = /^([01]\d|2[0-3]):[0-5]\d$/;
|
|
24
|
+
/** One cron list element: `*`, `n`, `a-b`, with an optional `/step`. */
|
|
25
|
+
const CRON_ELEMENT_PATTERN = /^(\*|\d+(-\d+)?)(\/\d+)?$/;
|
|
26
|
+
/** Allowed ranges of the five cron fields. */
|
|
27
|
+
const CRON_RANGES = [[0, 59], [0, 23], [1, 31], [1, 12], [0, 6]];
|
|
28
|
+
/**
|
|
29
|
+
* Whether a raw template carries a `bundle` section.
|
|
30
|
+
*
|
|
31
|
+
* @param raw - Parsed template JSON
|
|
32
|
+
* @returns True when `bundle` is an object
|
|
33
|
+
*/
|
|
34
|
+
export function hasBundleSection(raw) {
|
|
35
|
+
return !!raw && typeof raw === 'object' && !!raw.bundle && typeof raw.bundle === 'object';
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Check a five-field cron expression (`*`, numbers, ranges, lists, steps).
|
|
39
|
+
*
|
|
40
|
+
* @param expression - Cron expression
|
|
41
|
+
* @returns True when every field is valid and in range
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* isValidCronExpression('0 9 * * 1-5') // true
|
|
45
|
+
*/
|
|
46
|
+
export function isValidCronExpression(expression) {
|
|
47
|
+
const fields = expression.trim().split(/\s+/);
|
|
48
|
+
if (fields.length !== 5)
|
|
49
|
+
return false;
|
|
50
|
+
return fields.every((field, i) => {
|
|
51
|
+
const [min, max] = CRON_RANGES[i];
|
|
52
|
+
return field.split(',').every((element) => {
|
|
53
|
+
if (!CRON_ELEMENT_PATTERN.test(element))
|
|
54
|
+
return false;
|
|
55
|
+
const [range, step] = element.split('/');
|
|
56
|
+
if (step !== undefined && Number(step) < 1)
|
|
57
|
+
return false;
|
|
58
|
+
if (range === '*')
|
|
59
|
+
return true;
|
|
60
|
+
const [a, b] = range.split('-').map(Number);
|
|
61
|
+
if (a < min || a > max)
|
|
62
|
+
return false;
|
|
63
|
+
if (b !== undefined && (b < a || b > max))
|
|
64
|
+
return false;
|
|
65
|
+
return true;
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Parse a member reference.
|
|
71
|
+
*
|
|
72
|
+
* @param ref - `role` (main team) or `team/role`
|
|
73
|
+
* @returns The team key and role
|
|
74
|
+
*/
|
|
75
|
+
export function parseMemberRef(ref) {
|
|
76
|
+
const slash = ref.indexOf('/');
|
|
77
|
+
if (slash < 0)
|
|
78
|
+
return { teamKey: BUNDLE_CONSTANTS.MAIN_TEAM_KEY, role: ref };
|
|
79
|
+
return { teamKey: ref.slice(0, slash), role: ref.slice(slash + 1) };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The lead role of a list of roles: the first with hierarchyLevel 1 that can
|
|
83
|
+
* delegate, else the first role.
|
|
84
|
+
*
|
|
85
|
+
* @param roles - Roles
|
|
86
|
+
* @returns Role id, or '' when there are no roles
|
|
87
|
+
*/
|
|
88
|
+
export function leadRoleOf(roles) {
|
|
89
|
+
return (roles.find((r) => r.hierarchyLevel === 1 && r.canDelegate) ?? roles[0])?.role ?? '';
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Every team of a bundle: the template's own roles as the main team, then
|
|
93
|
+
* `bundle.teams`.
|
|
94
|
+
*
|
|
95
|
+
* @param template - A validated bundle template
|
|
96
|
+
* @returns Teams, main first
|
|
97
|
+
*/
|
|
98
|
+
export function bundleTeams(template) {
|
|
99
|
+
const main = {
|
|
100
|
+
key: BUNDLE_CONSTANTS.MAIN_TEAM_KEY,
|
|
101
|
+
name: template.bundle.teamName ?? template.name,
|
|
102
|
+
description: template.description,
|
|
103
|
+
roles: template.roles,
|
|
104
|
+
leadRole: leadRoleOf(template.roles),
|
|
105
|
+
};
|
|
106
|
+
const extra = (template.bundle.teams ?? []).map((t) => ({
|
|
107
|
+
key: t.key,
|
|
108
|
+
name: t.name,
|
|
109
|
+
description: t.description ?? '',
|
|
110
|
+
roles: t.roles,
|
|
111
|
+
leadRole: leadRoleOf(t.roles),
|
|
112
|
+
}));
|
|
113
|
+
return [main, ...extra];
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Whether a relative file path stays inside the template directory.
|
|
117
|
+
*
|
|
118
|
+
* @param file - Path from the manifest
|
|
119
|
+
* @returns True for a plain relative path without `..`
|
|
120
|
+
*/
|
|
121
|
+
function isSafeRelativePath(file) {
|
|
122
|
+
if (!file || path.isAbsolute(file))
|
|
123
|
+
return false;
|
|
124
|
+
return !file.split(/[\\/]/).includes('..');
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Validate a question list.
|
|
128
|
+
*
|
|
129
|
+
* @param questions - Questions (unknown shape)
|
|
130
|
+
* @param errors - Collects problems
|
|
131
|
+
* @returns The ids of the well-formed questions
|
|
132
|
+
*/
|
|
133
|
+
function validateQuestions(questions, errors) {
|
|
134
|
+
const ids = new Set();
|
|
135
|
+
if (questions === undefined)
|
|
136
|
+
return ids;
|
|
137
|
+
if (!Array.isArray(questions)) {
|
|
138
|
+
errors.push('bundle.questions: must be an array');
|
|
139
|
+
return ids;
|
|
140
|
+
}
|
|
141
|
+
questions.forEach((raw, i) => {
|
|
142
|
+
const q = raw;
|
|
143
|
+
const at = `bundle.questions[${i}]`;
|
|
144
|
+
if (typeof q.id !== 'string' || !QUESTION_ID_PATTERN.test(q.id)) {
|
|
145
|
+
errors.push(`${at}.id: lower snake case required (it is the placeholder name)`);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (ids.has(q.id))
|
|
149
|
+
errors.push(`${at}.id: duplicate "${q.id}"`);
|
|
150
|
+
if (BUNDLE_CONSTANTS.BUILTIN_PLACEHOLDERS.includes(q.id))
|
|
151
|
+
errors.push(`${at}.id: "${q.id}" is a built-in placeholder`);
|
|
152
|
+
ids.add(q.id);
|
|
153
|
+
if (typeof q.label !== 'string' || q.label.trim() === '')
|
|
154
|
+
errors.push(`${at}.label: required`);
|
|
155
|
+
if (!['text', 'textarea', 'select', 'multiselect'].includes(q.type))
|
|
156
|
+
errors.push(`${at}.type: text | textarea | select | multiselect`);
|
|
157
|
+
if (typeof q.required !== 'boolean')
|
|
158
|
+
errors.push(`${at}.required: boolean required`);
|
|
159
|
+
const choice = q.type === 'select' || q.type === 'multiselect';
|
|
160
|
+
const values = new Set();
|
|
161
|
+
if (choice) {
|
|
162
|
+
if (!Array.isArray(q.options) || q.options.length === 0) {
|
|
163
|
+
errors.push(`${at}.options: a ${q.type} question needs options`);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
q.options.forEach((o, j) => {
|
|
167
|
+
if (!o || typeof o.value !== 'string' || o.value === '')
|
|
168
|
+
errors.push(`${at}.options[${j}].value: required`);
|
|
169
|
+
else
|
|
170
|
+
values.add(o.value);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (q.required === false && q.default === undefined) {
|
|
175
|
+
errors.push(`${at}.default: an optional question needs a default (may be "")`);
|
|
176
|
+
}
|
|
177
|
+
if (q.default !== undefined) {
|
|
178
|
+
const defaults = Array.isArray(q.default) ? q.default : [q.default];
|
|
179
|
+
if (!defaults.every((d) => typeof d === 'string'))
|
|
180
|
+
errors.push(`${at}.default: string or string[]`);
|
|
181
|
+
if (choice && values.size > 0) {
|
|
182
|
+
const bad = defaults.filter((d) => d !== '' && !values.has(d));
|
|
183
|
+
if (bad.length > 0)
|
|
184
|
+
errors.push(`${at}.default: not an option: ${bad.join(', ')}`);
|
|
185
|
+
}
|
|
186
|
+
if (q.type === 'select' && Array.isArray(q.default))
|
|
187
|
+
errors.push(`${at}.default: a select default is one value`);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
return ids;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Validate the roles of one team.
|
|
194
|
+
*
|
|
195
|
+
* @param roles - Roles (unknown shape)
|
|
196
|
+
* @param at - Path for messages
|
|
197
|
+
* @param errors - Collects problems
|
|
198
|
+
* @returns Role ids of the well-formed roles
|
|
199
|
+
*/
|
|
200
|
+
function validateRoles(roles, at, errors) {
|
|
201
|
+
const ids = new Set();
|
|
202
|
+
if (!Array.isArray(roles) || roles.length === 0) {
|
|
203
|
+
errors.push(`${at}: at least one role (the "roles" format; members[] templates cannot be bundles)`);
|
|
204
|
+
return ids;
|
|
205
|
+
}
|
|
206
|
+
const names = new Set();
|
|
207
|
+
roles.forEach((raw, i) => {
|
|
208
|
+
if (!isValidTemplateRole(raw)) {
|
|
209
|
+
errors.push(`${at}[${i}]: needs role, label, defaultName, count ≥ 1, hierarchyLevel, canDelegate, defaultSkills`);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (ids.has(raw.role))
|
|
213
|
+
errors.push(`${at}[${i}].role: duplicate "${raw.role}"`);
|
|
214
|
+
ids.add(raw.role);
|
|
215
|
+
if (!ASCII_NAME_PATTERN.test(raw.defaultName)) {
|
|
216
|
+
errors.push(`${at}[${i}].defaultName: ASCII letters, digits, space, - or _ (session names are built from it); put the Chinese title in jobTitle`);
|
|
217
|
+
}
|
|
218
|
+
const lower = raw.defaultName.toLowerCase();
|
|
219
|
+
if (names.has(lower))
|
|
220
|
+
errors.push(`${at}[${i}].defaultName: duplicate "${raw.defaultName}"`);
|
|
221
|
+
names.add(lower);
|
|
222
|
+
});
|
|
223
|
+
const typed = roles.filter(isValidTemplateRole);
|
|
224
|
+
if (typed.length > 0 && !typed.some((r) => r.hierarchyLevel === 1 && r.canDelegate)) {
|
|
225
|
+
errors.push(`${at}: one role must be the lead (hierarchyLevel 1, canDelegate true)`);
|
|
226
|
+
}
|
|
227
|
+
for (const r of typed) {
|
|
228
|
+
if (r.reportsTo && !ids.has(r.reportsTo))
|
|
229
|
+
errors.push(`${at}: "${r.role}" reports to unknown role "${r.reportsTo}"`);
|
|
230
|
+
}
|
|
231
|
+
return ids;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Validate a template that carries a `bundle` section.
|
|
235
|
+
*
|
|
236
|
+
* @param raw - Parsed template JSON
|
|
237
|
+
* @param options - File access for `file` references (optional)
|
|
238
|
+
* @returns `{ ok, errors }`
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* const { ok, errors } = validateBundleTemplate(JSON.parse(text), { dir, readFile });
|
|
242
|
+
*/
|
|
243
|
+
export function validateBundleTemplate(raw, options = {}) {
|
|
244
|
+
const errors = [];
|
|
245
|
+
if (!raw || typeof raw !== 'object')
|
|
246
|
+
return { ok: false, errors: ['template: must be an object'] };
|
|
247
|
+
const t = raw;
|
|
248
|
+
if (typeof t.id !== 'string' || !SLUG_PATTERN.test(t.id))
|
|
249
|
+
errors.push('id: kebab-case required');
|
|
250
|
+
if (typeof t.name !== 'string' || t.name.trim() === '')
|
|
251
|
+
errors.push('name: required');
|
|
252
|
+
if (typeof t.description !== 'string')
|
|
253
|
+
errors.push('description: required');
|
|
254
|
+
if (!hasBundleSection(t)) {
|
|
255
|
+
errors.push('bundle: section required');
|
|
256
|
+
return { ok: false, errors };
|
|
257
|
+
}
|
|
258
|
+
const b = t.bundle;
|
|
259
|
+
if (b.schemaVersion !== BUNDLE_CONSTANTS.SCHEMA_VERSION)
|
|
260
|
+
errors.push(`bundle.schemaVersion: must be ${BUNDLE_CONSTANTS.SCHEMA_VERSION}`);
|
|
261
|
+
if (b.status !== undefined && !Object.values(BUNDLE_CONSTANTS.STATUS).includes(b.status))
|
|
262
|
+
errors.push('bundle.status: ready | draft');
|
|
263
|
+
for (const key of ['label', 'tagline', 'ownerSummary']) {
|
|
264
|
+
if (typeof b[key] !== 'string' || b[key].trim() === '')
|
|
265
|
+
errors.push(`bundle.${key}: required`);
|
|
266
|
+
}
|
|
267
|
+
const runtimes = Object.values(RUNTIME_TYPES);
|
|
268
|
+
if (!b.runtime || !runtimes.includes(b.runtime.recommended)) {
|
|
269
|
+
errors.push(`bundle.runtime.recommended: one of ${runtimes.join(', ')}`);
|
|
270
|
+
}
|
|
271
|
+
else if (b.runtime.compatible && !b.runtime.compatible.every((r) => runtimes.includes(r))) {
|
|
272
|
+
errors.push('bundle.runtime.compatible: unknown runtime');
|
|
273
|
+
}
|
|
274
|
+
if (!b.server || !BUNDLE_CONSTANTS.SERVER_TIERS.includes(b.server.tier)) {
|
|
275
|
+
errors.push(`bundle.server.tier: one of ${BUNDLE_CONSTANTS.SERVER_TIERS.join(', ')}`);
|
|
276
|
+
}
|
|
277
|
+
// Teams and roles
|
|
278
|
+
const mainRoles = validateRoles(t.roles, 'roles', errors);
|
|
279
|
+
const rolesByTeam = new Map([[BUNDLE_CONSTANTS.MAIN_TEAM_KEY, mainRoles]]);
|
|
280
|
+
if (b.teams !== undefined) {
|
|
281
|
+
if (!Array.isArray(b.teams))
|
|
282
|
+
errors.push('bundle.teams: must be an array');
|
|
283
|
+
else {
|
|
284
|
+
b.teams.forEach((team, i) => {
|
|
285
|
+
const at = `bundle.teams[${i}]`;
|
|
286
|
+
if (!team || typeof team.key !== 'string' || !SLUG_PATTERN.test(team.key)) {
|
|
287
|
+
errors.push(`${at}.key: kebab-case required`);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (rolesByTeam.has(team.key))
|
|
291
|
+
errors.push(`${at}.key: duplicate "${team.key}" (main is reserved)`);
|
|
292
|
+
if (typeof team.name !== 'string' || team.name.trim() === '')
|
|
293
|
+
errors.push(`${at}.name: required`);
|
|
294
|
+
rolesByTeam.set(team.key, validateRoles(team.roles, `${at}.roles`, errors));
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const questionIds = validateQuestions(b.questions, errors);
|
|
299
|
+
const known = new Set([...questionIds, ...BUNDLE_CONSTANTS.BUILTIN_PLACEHOLDERS]);
|
|
300
|
+
const checkText = (text, at) => {
|
|
301
|
+
if (typeof text !== 'string')
|
|
302
|
+
return;
|
|
303
|
+
const unknown = listPlaceholders(text).filter((p) => !known.has(p));
|
|
304
|
+
if (unknown.length > 0)
|
|
305
|
+
errors.push(`${at}: placeholder without a question: {{${unknown.join('}}, {{')}}}`);
|
|
306
|
+
};
|
|
307
|
+
const checkRef = (ref, at, allowStar = false) => {
|
|
308
|
+
if (typeof ref !== 'string' || ref === '') {
|
|
309
|
+
errors.push(`${at}: member ref required`);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
if (allowStar && ref === '*')
|
|
313
|
+
return;
|
|
314
|
+
const { teamKey, role } = parseMemberRef(ref);
|
|
315
|
+
const roles = rolesByTeam.get(teamKey);
|
|
316
|
+
if (!roles)
|
|
317
|
+
errors.push(`${at}: unknown team "${teamKey}"`);
|
|
318
|
+
else if (!roles.has(role))
|
|
319
|
+
errors.push(`${at}: unknown role "${role}" in team "${teamKey}"`);
|
|
320
|
+
};
|
|
321
|
+
const checkTeamKey = (key, at) => {
|
|
322
|
+
if (key !== undefined && (typeof key !== 'string' || !rolesByTeam.has(key)))
|
|
323
|
+
errors.push(`${at}: unknown team "${String(key)}"`);
|
|
324
|
+
};
|
|
325
|
+
// Placeholders in prompts and names
|
|
326
|
+
checkText(b.teamName, 'bundle.teamName');
|
|
327
|
+
checkText(b.timezone, 'bundle.timezone');
|
|
328
|
+
checkText(t.mission, 'mission');
|
|
329
|
+
const allRoleLists = [['roles', t.roles], ...(Array.isArray(b.teams) ? b.teams.map((team, i) => [`bundle.teams[${i}].roles`, team?.roles]) : [])];
|
|
330
|
+
for (const [at, roles] of allRoleLists) {
|
|
331
|
+
if (!Array.isArray(roles))
|
|
332
|
+
continue;
|
|
333
|
+
roles.forEach((r, i) => {
|
|
334
|
+
checkText(r?.promptAdditions, `${at}[${i}].promptAdditions`);
|
|
335
|
+
checkText(r?.jobDescription, `${at}[${i}].jobDescription`);
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
if (Array.isArray(b.teams)) {
|
|
339
|
+
b.teams.forEach((team, i) => {
|
|
340
|
+
checkText(team?.name, `bundle.teams[${i}].name`);
|
|
341
|
+
checkText(team?.description, `bundle.teams[${i}].description`);
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
// Norms and SOPs
|
|
345
|
+
const docLists = [['norms', b.norms], ['sops', b.sops]];
|
|
346
|
+
for (const [kind, list] of docLists) {
|
|
347
|
+
if (list === undefined)
|
|
348
|
+
continue;
|
|
349
|
+
if (!Array.isArray(list)) {
|
|
350
|
+
errors.push(`bundle.${kind}: must be an array`);
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
const seen = new Set();
|
|
354
|
+
list.forEach((doc, i) => {
|
|
355
|
+
const at = `bundle.${kind}[${i}]`;
|
|
356
|
+
if (typeof doc?.id !== 'string' || !SLUG_PATTERN.test(doc.id))
|
|
357
|
+
errors.push(`${at}.id: kebab-case required`);
|
|
358
|
+
else {
|
|
359
|
+
const key = `${String(doc.team ?? BUNDLE_CONSTANTS.MAIN_TEAM_KEY)}/${String(doc.category ?? '')}/${doc.id}`;
|
|
360
|
+
if (seen.has(key))
|
|
361
|
+
errors.push(`${at}.id: duplicate "${doc.id}"`);
|
|
362
|
+
seen.add(key);
|
|
363
|
+
if (kind === 'norms' && doc.id === BUNDLE_CONSTANTS.REVIEW_POINTS_NORM_ID)
|
|
364
|
+
errors.push(`${at}.id: "${doc.id}" is written from reviewPoints`);
|
|
365
|
+
}
|
|
366
|
+
if (typeof doc?.title !== 'string' || doc.title.trim() === '')
|
|
367
|
+
errors.push(`${at}.title: required`);
|
|
368
|
+
if (doc?.category !== undefined && (typeof doc.category !== 'string' || !SLUG_PATTERN.test(doc.category)))
|
|
369
|
+
errors.push(`${at}.category: kebab-case`);
|
|
370
|
+
checkTeamKey(doc?.team, `${at}.team`);
|
|
371
|
+
const hasContent = typeof doc?.content === 'string' && doc.content.trim() !== '';
|
|
372
|
+
const hasFile = typeof doc?.file === 'string';
|
|
373
|
+
if (hasContent === hasFile) {
|
|
374
|
+
errors.push(`${at}: exactly one of content / file`);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
if (hasContent)
|
|
378
|
+
checkText(doc.content, `${at}.content`);
|
|
379
|
+
if (hasFile) {
|
|
380
|
+
const file = doc.file;
|
|
381
|
+
if (!isSafeRelativePath(file)) {
|
|
382
|
+
errors.push(`${at}.file: relative path inside the template directory`);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
if (options.dir && options.readFile) {
|
|
386
|
+
const body = options.readFile(path.join(options.dir, file));
|
|
387
|
+
if (body === null)
|
|
388
|
+
errors.push(`${at}.file: not found: ${file}`);
|
|
389
|
+
else
|
|
390
|
+
checkText(body, `${at}.file ${file}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
// Review points
|
|
396
|
+
if (b.reviewPoints !== undefined) {
|
|
397
|
+
if (!Array.isArray(b.reviewPoints))
|
|
398
|
+
errors.push('bundle.reviewPoints: must be an array');
|
|
399
|
+
else
|
|
400
|
+
b.reviewPoints.forEach((rp, i) => {
|
|
401
|
+
const at = `bundle.reviewPoints[${i}]`;
|
|
402
|
+
if (typeof rp?.id !== 'string' || !SLUG_PATTERN.test(rp.id))
|
|
403
|
+
errors.push(`${at}.id: kebab-case required`);
|
|
404
|
+
if (typeof rp?.what !== 'string' || rp.what.trim() === '')
|
|
405
|
+
errors.push(`${at}.what: required`);
|
|
406
|
+
if (rp?.approver !== 'owner' && rp?.approver !== 'lead')
|
|
407
|
+
errors.push(`${at}.approver: owner | lead`);
|
|
408
|
+
checkText(rp?.what, `${at}.what`);
|
|
409
|
+
checkText(rp?.how, `${at}.how`);
|
|
410
|
+
checkTeamKey(rp?.team, `${at}.team`);
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
// Skills
|
|
414
|
+
if (b.skills !== undefined) {
|
|
415
|
+
const lists = [['required', b.skills?.required], ['optional', b.skills?.optional ?? []]];
|
|
416
|
+
for (const [key, list] of lists) {
|
|
417
|
+
if (!Array.isArray(list) || !list.every((s) => typeof s === 'string' && SLUG_PATTERN.test(s))) {
|
|
418
|
+
errors.push(`bundle.skills.${key}: array of skill ids`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
// Connectors
|
|
423
|
+
if (b.connectors !== undefined) {
|
|
424
|
+
if (!Array.isArray(b.connectors))
|
|
425
|
+
errors.push('bundle.connectors: must be an array');
|
|
426
|
+
else
|
|
427
|
+
b.connectors.forEach((c, i) => {
|
|
428
|
+
const at = `bundle.connectors[${i}]`;
|
|
429
|
+
if (!c || !BUNDLE_CONSTANTS.CONNECTOR_IDS.includes(c.id))
|
|
430
|
+
errors.push(`${at}.id: one of ${BUNDLE_CONSTANTS.CONNECTOR_IDS.join(', ')}`);
|
|
431
|
+
if (typeof c?.required !== 'boolean')
|
|
432
|
+
errors.push(`${at}.required: boolean required`);
|
|
433
|
+
if (typeof c?.why !== 'string' || c.why.trim() === '')
|
|
434
|
+
errors.push(`${at}.why: required (owner-facing)`);
|
|
435
|
+
if (c?.products !== undefined) {
|
|
436
|
+
if (c.id !== 'google-workspace')
|
|
437
|
+
errors.push(`${at}.products: only for google-workspace`);
|
|
438
|
+
else if (!Array.isArray(c.products) || !c.products.every((p) => BUNDLE_CONSTANTS.GOOGLE_PRODUCTS.includes(p))) {
|
|
439
|
+
errors.push(`${at}.products: ${BUNDLE_CONSTANTS.GOOGLE_PRODUCTS.join(' | ')}`);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
// Slack
|
|
445
|
+
if (b.slack !== undefined) {
|
|
446
|
+
const channels = b.slack?.channels ?? [];
|
|
447
|
+
if (!Array.isArray(channels))
|
|
448
|
+
errors.push('bundle.slack.channels: must be an array');
|
|
449
|
+
else {
|
|
450
|
+
const keys = new Set();
|
|
451
|
+
channels.forEach((ch, i) => {
|
|
452
|
+
const at = `bundle.slack.channels[${i}]`;
|
|
453
|
+
if (typeof ch?.key !== 'string' || !SLUG_PATTERN.test(ch.key))
|
|
454
|
+
errors.push(`${at}.key: kebab-case required`);
|
|
455
|
+
else if (keys.has(ch.key))
|
|
456
|
+
errors.push(`${at}.key: duplicate "${ch.key}"`);
|
|
457
|
+
else
|
|
458
|
+
keys.add(ch.key);
|
|
459
|
+
if (typeof ch?.name !== 'string' || ch.name.trim() === '')
|
|
460
|
+
errors.push(`${at}.name: required`);
|
|
461
|
+
checkText(ch?.name, `${at}.name`);
|
|
462
|
+
checkText(ch?.purpose, `${at}.purpose`);
|
|
463
|
+
if (!Array.isArray(ch?.members) || ch.members.length === 0)
|
|
464
|
+
errors.push(`${at}.members: at least one ref or "*"`);
|
|
465
|
+
else
|
|
466
|
+
ch.members.forEach((ref, j) => checkRef(ref, `${at}.members[${j}]`, true));
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
// Schedules
|
|
471
|
+
if (b.schedules !== undefined) {
|
|
472
|
+
if (!Array.isArray(b.schedules))
|
|
473
|
+
errors.push('bundle.schedules: must be an array');
|
|
474
|
+
else {
|
|
475
|
+
const ids = new Set();
|
|
476
|
+
b.schedules.forEach((s, i) => {
|
|
477
|
+
const at = `bundle.schedules[${i}]`;
|
|
478
|
+
if (typeof s?.id !== 'string' || !SLUG_PATTERN.test(s.id))
|
|
479
|
+
errors.push(`${at}.id: kebab-case required`);
|
|
480
|
+
else if (ids.has(s.id))
|
|
481
|
+
errors.push(`${at}.id: duplicate "${s.id}"`);
|
|
482
|
+
else
|
|
483
|
+
ids.add(s.id);
|
|
484
|
+
if (typeof s?.title !== 'string' || s.title.trim() === '')
|
|
485
|
+
errors.push(`${at}.title: required`);
|
|
486
|
+
if (typeof s?.cron !== 'string' || !isValidCronExpression(s.cron))
|
|
487
|
+
errors.push(`${at}.cron: five-field cron expression`);
|
|
488
|
+
if (typeof s?.task !== 'string' || s.task.trim() === '')
|
|
489
|
+
errors.push(`${at}.task: required`);
|
|
490
|
+
checkText(s?.task, `${at}.task`);
|
|
491
|
+
checkText(s?.timezone, `${at}.timezone`);
|
|
492
|
+
if (s?.target !== undefined)
|
|
493
|
+
checkRef(s.target, `${at}.target`);
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
// First week
|
|
498
|
+
if (b.firstWeek !== undefined) {
|
|
499
|
+
if (!Array.isArray(b.firstWeek))
|
|
500
|
+
errors.push('bundle.firstWeek: must be an array');
|
|
501
|
+
else {
|
|
502
|
+
const ids = new Set();
|
|
503
|
+
b.firstWeek.forEach((task, i) => {
|
|
504
|
+
const at = `bundle.firstWeek[${i}]`;
|
|
505
|
+
if (typeof task?.id !== 'string' || !SLUG_PATTERN.test(task.id))
|
|
506
|
+
errors.push(`${at}.id: kebab-case required`);
|
|
507
|
+
else if (ids.has(task.id))
|
|
508
|
+
errors.push(`${at}.id: duplicate "${task.id}"`);
|
|
509
|
+
else
|
|
510
|
+
ids.add(task.id);
|
|
511
|
+
if (!Number.isInteger(task?.day) || task.day < 0 || task.day > BUNDLE_CONSTANTS.MAX_FIRST_WEEK_DAY) {
|
|
512
|
+
errors.push(`${at}.day: integer 0-${BUNDLE_CONSTANTS.MAX_FIRST_WEEK_DAY}`);
|
|
513
|
+
}
|
|
514
|
+
if (task?.time !== undefined && (typeof task.time !== 'string' || !TIME_PATTERN.test(task.time)))
|
|
515
|
+
errors.push(`${at}.time: HH:MM`);
|
|
516
|
+
if (typeof task?.title !== 'string' || task.title.trim() === '')
|
|
517
|
+
errors.push(`${at}.title: required`);
|
|
518
|
+
if (typeof task?.task !== 'string' || task.task.trim() === '')
|
|
519
|
+
errors.push(`${at}.task: required`);
|
|
520
|
+
checkText(task?.title, `${at}.title`);
|
|
521
|
+
checkText(task?.task, `${at}.task`);
|
|
522
|
+
if (task?.target !== undefined)
|
|
523
|
+
checkRef(task.target, `${at}.target`);
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (b.todo !== undefined && (!Array.isArray(b.todo) || !b.todo.every((x) => typeof x === 'string'))) {
|
|
528
|
+
errors.push('bundle.todo: array of strings');
|
|
529
|
+
}
|
|
530
|
+
return { ok: errors.length === 0, errors };
|
|
531
|
+
}
|
|
532
|
+
//# sourceMappingURL=bundle-manifest.js.map
|