@ploaness/governance 1.0.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.
Files changed (199) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +13 -0
  3. package/dist/access-boundary.d.ts +41 -0
  4. package/dist/access-boundary.d.ts.map +1 -0
  5. package/dist/access-boundary.js +26 -0
  6. package/dist/access-boundary.js.map +1 -0
  7. package/dist/agent-references.d.ts +12 -0
  8. package/dist/agent-references.d.ts.map +1 -0
  9. package/dist/agent-references.js +53 -0
  10. package/dist/agent-references.js.map +1 -0
  11. package/dist/architecture-layers.d.ts +11 -0
  12. package/dist/architecture-layers.d.ts.map +1 -0
  13. package/dist/architecture-layers.js +47 -0
  14. package/dist/architecture-layers.js.map +1 -0
  15. package/dist/asset-policy.d.ts +138 -0
  16. package/dist/asset-policy.d.ts.map +1 -0
  17. package/dist/asset-policy.js +312 -0
  18. package/dist/asset-policy.js.map +1 -0
  19. package/dist/banned-typography.d.ts +13 -0
  20. package/dist/banned-typography.d.ts.map +1 -0
  21. package/dist/banned-typography.js +78 -0
  22. package/dist/banned-typography.js.map +1 -0
  23. package/dist/bundle-budget.d.ts +16 -0
  24. package/dist/bundle-budget.d.ts.map +1 -0
  25. package/dist/bundle-budget.js +37 -0
  26. package/dist/bundle-budget.js.map +1 -0
  27. package/dist/commit-message.d.ts +37 -0
  28. package/dist/commit-message.d.ts.map +1 -0
  29. package/dist/commit-message.js +144 -0
  30. package/dist/commit-message.js.map +1 -0
  31. package/dist/config-references.d.ts +17 -0
  32. package/dist/config-references.d.ts.map +1 -0
  33. package/dist/config-references.js +54 -0
  34. package/dist/config-references.js.map +1 -0
  35. package/dist/container-failure.d.ts +40 -0
  36. package/dist/container-failure.d.ts.map +1 -0
  37. package/dist/container-failure.js +154 -0
  38. package/dist/container-failure.js.map +1 -0
  39. package/dist/container-files.d.ts +36 -0
  40. package/dist/container-files.d.ts.map +1 -0
  41. package/dist/container-files.js +74 -0
  42. package/dist/container-files.js.map +1 -0
  43. package/dist/container-freshness.d.ts +102 -0
  44. package/dist/container-freshness.d.ts.map +1 -0
  45. package/dist/container-freshness.js +129 -0
  46. package/dist/container-freshness.js.map +1 -0
  47. package/dist/dependency-freshness.d.ts +129 -0
  48. package/dist/dependency-freshness.d.ts.map +1 -0
  49. package/dist/dependency-freshness.js +161 -0
  50. package/dist/dependency-freshness.js.map +1 -0
  51. package/dist/doc-blocks.d.ts +13 -0
  52. package/dist/doc-blocks.d.ts.map +1 -0
  53. package/dist/doc-blocks.js +75 -0
  54. package/dist/doc-blocks.js.map +1 -0
  55. package/dist/document-references.d.ts +44 -0
  56. package/dist/document-references.d.ts.map +1 -0
  57. package/dist/document-references.js +94 -0
  58. package/dist/document-references.js.map +1 -0
  59. package/dist/editorconfig-policy.d.ts +24 -0
  60. package/dist/editorconfig-policy.d.ts.map +1 -0
  61. package/dist/editorconfig-policy.js +106 -0
  62. package/dist/editorconfig-policy.js.map +1 -0
  63. package/dist/environment-files.d.ts +42 -0
  64. package/dist/environment-files.d.ts.map +1 -0
  65. package/dist/environment-files.js +76 -0
  66. package/dist/environment-files.js.map +1 -0
  67. package/dist/file-roles.d.ts +39 -0
  68. package/dist/file-roles.d.ts.map +1 -0
  69. package/dist/file-roles.js +82 -0
  70. package/dist/file-roles.js.map +1 -0
  71. package/dist/generated-denial.d.ts +42 -0
  72. package/dist/generated-denial.d.ts.map +1 -0
  73. package/dist/generated-denial.js +87 -0
  74. package/dist/generated-denial.js.map +1 -0
  75. package/dist/history-policy.d.ts +32 -0
  76. package/dist/history-policy.d.ts.map +1 -0
  77. package/dist/history-policy.js +50 -0
  78. package/dist/history-policy.js.map +1 -0
  79. package/dist/image-assets.d.ts +14 -0
  80. package/dist/image-assets.d.ts.map +1 -0
  81. package/dist/image-assets.js +205 -0
  82. package/dist/image-assets.js.map +1 -0
  83. package/dist/index.d.ts +49 -0
  84. package/dist/index.d.ts.map +1 -0
  85. package/dist/index.js +53 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/install-policy.d.ts +35 -0
  88. package/dist/install-policy.d.ts.map +1 -0
  89. package/dist/install-policy.js +61 -0
  90. package/dist/install-policy.js.map +1 -0
  91. package/dist/json-shapes.d.ts +86 -0
  92. package/dist/json-shapes.d.ts.map +1 -0
  93. package/dist/json-shapes.js +124 -0
  94. package/dist/json-shapes.js.map +1 -0
  95. package/dist/license-allowlist.d.ts +15 -0
  96. package/dist/license-allowlist.d.ts.map +1 -0
  97. package/dist/license-allowlist.js +157 -0
  98. package/dist/license-allowlist.js.map +1 -0
  99. package/dist/network-policy.d.ts +69 -0
  100. package/dist/network-policy.d.ts.map +1 -0
  101. package/dist/network-policy.js +173 -0
  102. package/dist/network-policy.js.map +1 -0
  103. package/dist/paired-assets.d.ts +34 -0
  104. package/dist/paired-assets.d.ts.map +1 -0
  105. package/dist/paired-assets.js +63 -0
  106. package/dist/paired-assets.js.map +1 -0
  107. package/dist/payload-access.d.ts +9 -0
  108. package/dist/payload-access.d.ts.map +1 -0
  109. package/dist/payload-access.js +168 -0
  110. package/dist/payload-access.js.map +1 -0
  111. package/dist/payload-policy.d.ts +18 -0
  112. package/dist/payload-policy.d.ts.map +1 -0
  113. package/dist/payload-policy.js +124 -0
  114. package/dist/payload-policy.js.map +1 -0
  115. package/dist/payload-source.d.ts +29 -0
  116. package/dist/payload-source.d.ts.map +1 -0
  117. package/dist/payload-source.js +84 -0
  118. package/dist/payload-source.js.map +1 -0
  119. package/dist/release-age.d.ts +23 -0
  120. package/dist/release-age.d.ts.map +1 -0
  121. package/dist/release-age.js +38 -0
  122. package/dist/release-age.js.map +1 -0
  123. package/dist/request-limits.d.ts +39 -0
  124. package/dist/request-limits.d.ts.map +1 -0
  125. package/dist/request-limits.js +57 -0
  126. package/dist/request-limits.js.map +1 -0
  127. package/dist/run-plan.d.ts +32 -0
  128. package/dist/run-plan.d.ts.map +1 -0
  129. package/dist/run-plan.js +43 -0
  130. package/dist/run-plan.js.map +1 -0
  131. package/dist/run-policy.d.ts +32 -0
  132. package/dist/run-policy.d.ts.map +1 -0
  133. package/dist/run-policy.js +37 -0
  134. package/dist/run-policy.js.map +1 -0
  135. package/dist/secret-policy.d.ts +13 -0
  136. package/dist/secret-policy.d.ts.map +1 -0
  137. package/dist/secret-policy.js +69 -0
  138. package/dist/secret-policy.js.map +1 -0
  139. package/dist/settings-layering.d.ts +45 -0
  140. package/dist/settings-layering.d.ts.map +1 -0
  141. package/dist/settings-layering.js +134 -0
  142. package/dist/settings-layering.js.map +1 -0
  143. package/dist/settings.d.ts +238 -0
  144. package/dist/settings.d.ts.map +1 -0
  145. package/dist/settings.js +378 -0
  146. package/dist/settings.js.map +1 -0
  147. package/dist/skill-manifest.d.ts +18 -0
  148. package/dist/skill-manifest.d.ts.map +1 -0
  149. package/dist/skill-manifest.js +125 -0
  150. package/dist/skill-manifest.js.map +1 -0
  151. package/dist/source-text.d.ts +83 -0
  152. package/dist/source-text.d.ts.map +1 -0
  153. package/dist/source-text.js +310 -0
  154. package/dist/source-text.js.map +1 -0
  155. package/dist/suppression-ceiling.d.ts +48 -0
  156. package/dist/suppression-ceiling.d.ts.map +1 -0
  157. package/dist/suppression-ceiling.js +115 -0
  158. package/dist/suppression-ceiling.js.map +1 -0
  159. package/dist/tailwind-tokens.d.ts +16 -0
  160. package/dist/tailwind-tokens.d.ts.map +1 -0
  161. package/dist/tailwind-tokens.js +70 -0
  162. package/dist/tailwind-tokens.js.map +1 -0
  163. package/dist/text-escapes.d.ts +7 -0
  164. package/dist/text-escapes.d.ts.map +1 -0
  165. package/dist/text-escapes.js +14 -0
  166. package/dist/text-escapes.js.map +1 -0
  167. package/dist/toolchain-pins.d.ts +42 -0
  168. package/dist/toolchain-pins.d.ts.map +1 -0
  169. package/dist/toolchain-pins.js +99 -0
  170. package/dist/toolchain-pins.js.map +1 -0
  171. package/dist/url-safety.d.ts +12 -0
  172. package/dist/url-safety.d.ts.map +1 -0
  173. package/dist/url-safety.js +58 -0
  174. package/dist/url-safety.js.map +1 -0
  175. package/dist/version-policy.d.ts +99 -0
  176. package/dist/version-policy.d.ts.map +1 -0
  177. package/dist/version-policy.js +306 -0
  178. package/dist/version-policy.js.map +1 -0
  179. package/dist/vulnerability-policy.d.ts +66 -0
  180. package/dist/vulnerability-policy.d.ts.map +1 -0
  181. package/dist/vulnerability-policy.js +106 -0
  182. package/dist/vulnerability-policy.js.map +1 -0
  183. package/dist/wiring-policy.d.ts +226 -0
  184. package/dist/wiring-policy.d.ts.map +1 -0
  185. package/dist/wiring-policy.js +458 -0
  186. package/dist/wiring-policy.js.map +1 -0
  187. package/dist/wiring-violation.d.ts +6 -0
  188. package/dist/wiring-violation.d.ts.map +1 -0
  189. package/dist/wiring-violation.js +8 -0
  190. package/dist/wiring-violation.js.map +1 -0
  191. package/dist/workspace-policy.d.ts +177 -0
  192. package/dist/workspace-policy.d.ts.map +1 -0
  193. package/dist/workspace-policy.js +228 -0
  194. package/dist/workspace-policy.js.map +1 -0
  195. package/dist/yaml-blocks.d.ts +43 -0
  196. package/dist/yaml-blocks.d.ts.map +1 -0
  197. package/dist/yaml-blocks.js +87 -0
  198. package/dist/yaml-blocks.js.map +1 -0
  199. package/package.json +41 -0
@@ -0,0 +1,177 @@
1
+ /** The repo-relative path of the member that sits at the repository root. */
2
+ export declare const ROOT_MEMBER_PATH: string;
3
+ /** A directory holding a package.json, and what that manifest declares. */
4
+ export interface ProjectManifest {
5
+ /** Repo-relative, {@link ROOT_MEMBER_PATH} for the repository root. */
6
+ readonly path: string;
7
+ readonly packageJson: unknown;
8
+ }
9
+ /** A governed member, reduced to what the repository-scope rules need to know about it. */
10
+ export interface MemberShape {
11
+ readonly path: string;
12
+ readonly isPayload: boolean;
13
+ /** The member's own source roots, used to decide whether a nested project is already covered. */
14
+ readonly sourceRoots: readonly string[];
15
+ }
16
+ /**
17
+ * Locate the repository root from the directories above the working directory.
18
+ *
19
+ * Deliberately not `git rev-parse --show-toplevel`. The integration fixtures run cases in directories
20
+ * that are never `git init`-ed, and asking git there walks out of the fixture and adopts whichever
21
+ * repository happens to contain the temporary directory - so a case would be judged against a tree
22
+ * nobody wrote. A workspace file is the stronger signal anyway: it is what pnpm itself reads to decide
23
+ * where the workspace begins.
24
+ * @param ancestors the working directory first, then each parent up to the filesystem root.
25
+ * @param hasEntry whether a directory holds a named entry.
26
+ * @returns the repository root, falling back to the working directory when nothing above it qualifies.
27
+ */
28
+ export declare const findRepositoryRoot: (ancestors: readonly string[], hasEntry: (directory: string, entry: string) => boolean) => string;
29
+ /**
30
+ * The directory patterns a workspace file declares.
31
+ * @param workspaceFile the contents of pnpm-workspace.yaml, or an empty string when absent.
32
+ * @returns one pattern per entry, in declaration order, including any leading-`!` exclusions.
33
+ */
34
+ export declare const readWorkspacePackages: (workspaceFile: string) => readonly string[];
35
+ /**
36
+ * The directories a workspace file selects as pnpm projects.
37
+ *
38
+ * The repository root is always a project whether or not `packages:` names it: it carries the manifest
39
+ * that declares the package manager, the engines and the scripts a run is invoked through, so a
40
+ * repository whose root were not a project would have no place to state any of them.
41
+ * @param patterns the declared directory patterns.
42
+ * @param directoriesWithManifest every repo-relative directory holding a tracked package.json.
43
+ * @returns the selected directories, root first, without duplicates.
44
+ */
45
+ export declare const selectProjects: (patterns: readonly string[], directoriesWithManifest: readonly string[]) => readonly string[];
46
+ /**
47
+ * Whether a project's manifest declares ploaness, which is what makes it a governed member.
48
+ *
49
+ * The marker is not a switch a project flips to escape. Under pnpm's strict layout a package that does
50
+ * not depend on ploaness cannot resolve `ploaness/biome`, `ploaness/eslint` or `ploaness/tsconfig.json`
51
+ * from its own configuration files, so its wiring could not work whatever this rule said - and dropping
52
+ * the declaration to avoid the gates is reported by {@link findUngovernedProjects} rather than obeyed.
53
+ * @param packageJson the project's parsed manifest.
54
+ * @returns true when ploaness is a declared dependency.
55
+ */
56
+ export declare const isGovernedProject: (packageJson: unknown) => boolean;
57
+ /**
58
+ * Whether a project's manifest declares Payload, which decides the third scope.
59
+ * @param packageJson the project's parsed manifest.
60
+ * @returns true when payload is a declared dependency.
61
+ */
62
+ export declare const isPayloadProject: (packageJson: unknown) => boolean;
63
+ /**
64
+ * What kind of package a member is, which decides the configurations it receives.
65
+ *
66
+ * Three kinds rather than the Payload/not-Payload pair this started as. A Next application that is not
67
+ * a Payload one - a public site reading a CMS over HTTP - has route handlers, a client bundle and a
68
+ * browser suite, so handing it a library's configuration would leave all three ungoverned; handing it
69
+ * Payload's would judge it against generated files it does not have. The kind is DERIVED from what the
70
+ * member declares and never declared directly, so a project cannot receive a weaker configuration by
71
+ * describing itself differently.
72
+ */
73
+ export type MemberKind = 'payload' | 'application' | 'library';
74
+ /**
75
+ * Decide a member's kind from its manifest.
76
+ * @param packageJson the member's parsed manifest.
77
+ * @returns `payload` when it declares Payload, `application` when it declares Next, else `library`.
78
+ */
79
+ export declare const memberKindOf: (packageJson: unknown) => MemberKind;
80
+ /**
81
+ * Whether a member has a server of its own, and therefore a build, a client bundle and a browser to
82
+ * drive. A library has none of the three, so the gates and configurations about them do not apply to it.
83
+ * @param kind the member's kind.
84
+ * @returns true for a Payload or Next application.
85
+ */
86
+ export declare const hasRuntime: (kind: MemberKind) => boolean;
87
+ /**
88
+ * The projects ploaness governs.
89
+ *
90
+ * A repository where nothing declares ploaness still has one member - its root - so a project that has
91
+ * not been initialised yet is reported by `preflight` and `wiring` rather than by an empty run that
92
+ * would look like a pass.
93
+ * @param projects every pnpm project the workspace selects.
94
+ * @returns the governed member paths, or the root alone when none qualifies.
95
+ */
96
+ export declare const findGovernedMembers: (projects: readonly ProjectManifest[]) => readonly string[];
97
+ /**
98
+ * Report every pnpm project ploaness is not judging.
99
+ *
100
+ * This is the anti-bypass half of the member marker. Dropping `ploaness` from a package's manifest
101
+ * removes it from the governed set, which without this rule would be a silent way to take a whole
102
+ * application out of verification.
103
+ * @param projects every pnpm project the workspace selects.
104
+ * @param members the governed members.
105
+ * @returns one finding per project that is neither governed nor inside a governed member's source.
106
+ */
107
+ export declare const findUngovernedProjects: (projects: readonly ProjectManifest[], members: readonly MemberShape[]) => readonly string[];
108
+ /**
109
+ * Verify the repository is a Payload project.
110
+ *
111
+ * Asked of the repository rather than of one directory, because a workspace legitimately holds packages
112
+ * that are not Payload applications. What ploaness refuses to judge is a repository with no Payload in
113
+ * it at all, which is the same refusal `preflight` always made, moved up one level.
114
+ * @param members the governed members.
115
+ * @returns a single finding when no member declares payload.
116
+ */
117
+ export declare const findPayloadMemberViolations: (members: readonly MemberShape[]) => readonly string[];
118
+ /** A member's declared origin, which the end-to-end run drives. */
119
+ export interface MemberOrigin {
120
+ readonly path: string;
121
+ readonly serverUrl: string;
122
+ }
123
+ /**
124
+ * Report members that would drive the same origin.
125
+ *
126
+ * The end-to-end configuration starts the application and reuses an existing server when one already
127
+ * answers. Two members sharing an origin therefore do not collide loudly: the second run finds the first
128
+ * application still listening, sweeps it, and passes - reporting on an application that was never under
129
+ * test. Nothing else in the harness can see that, and it exists only once more than one member has a
130
+ * browser suite.
131
+ * @param origins each member's path and declared origin.
132
+ * @returns one finding per member sharing an origin with an earlier one.
133
+ */
134
+ export declare const findServerUrlCollisions: (origins: readonly MemberOrigin[]) => readonly string[];
135
+ /**
136
+ * The patterns that stop an analyzer walking below this member into another member.
137
+ *
138
+ * A tool that reads everything under the directory it runs in has no idea where a package ends, and at a
139
+ * workspace root every sibling sits underneath. knip reported each sibling's entry point as an unused
140
+ * file that way; ESLint reported findings no member's own gate reports, because a shipped configuration
141
+ * reads its settings from the working directory - so the framework glue and the generated artefacts a
142
+ * member declares are absent from the configuration a root-started run renders, whichever config file
143
+ * that run resolves. Nothing is lost by stopping: each sibling is a separate analysis, under its own
144
+ * settings and its own entry points, which is what running the gate per member does.
145
+ *
146
+ * Shared rather than spelled out at each call site, because a boundary two analyzers disagree about is
147
+ * one analyzer reaching past it.
148
+ * @param siblingPaths the other members' paths, relative to this member's directory.
149
+ * @returns one recursive ignore pattern per sibling.
150
+ */
151
+ export declare const analysisBoundaries: (siblingPaths: readonly string[]) => readonly string[];
152
+ /** Whether an ESLint run applies its fixable rules or reports what it finds. */
153
+ export type EslintRunMode = 'fix' | 'report';
154
+ /**
155
+ * The arguments an ESLint run takes, whether it is judging a member or writing to one.
156
+ *
157
+ * The boundary has to ride on the command line rather than sit in a configuration file, because ESLint
158
+ * resolves its ignores from the working directory: a run started at a workspace root renders the ROOT's
159
+ * settings over a member's files, so the framework glue and generated artefacts that member declares are
160
+ * simply absent from what that run sees. `analysisBoundaries` states where to stop; this states that
161
+ * both runs are told.
162
+ *
163
+ * They were not. The gate carried the boundary and `ploaness format` did not, which is the worse of the
164
+ * two halves to get wrong: a judge reaching too far reports a finding somebody argues with, while a
165
+ * WRITER reaching too far edits files no gate will ever look at. A root-started format rewrote a Payload
166
+ * member's generated import map - stripping the `@type` annotation and rewriting an import - and every
167
+ * gate in the repository stayed green, because each member's own run correctly excludes that file.
168
+ *
169
+ * The mode flag is the only legitimate difference. `--max-warnings=0` is a verdict, which a run that
170
+ * writes does not render; `ploaness format` deliberately succeeds while findings remain, since the
171
+ * unfixable ones are for `ploaness verify` to report.
172
+ * @param siblingPaths the other members' paths, relative to this member's directory.
173
+ * @param mode whether this run writes fixes or renders a verdict.
174
+ * @returns the full argument list, the target first.
175
+ */
176
+ export declare const eslintArguments: (siblingPaths: readonly string[], mode: EslintRunMode) => readonly string[];
177
+ //# sourceMappingURL=workspace-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-policy.d.ts","sourceRoot":"","sources":["../src/workspace-policy.ts"],"names":[],"mappings":"AAaA,6EAA6E;AAC7E,eAAO,MAAM,gBAAgB,EAAE,MAAY,CAAA;AAK3C,2EAA2E;AAC3E,MAAM,WAAW,eAAe;IAC9B,uEAAuE;IACvE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;CAC9B;AAED,2FAA2F;AAC3F,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,iGAAiG;IACjG,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;CACxC;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB,GAC7B,WAAW,SAAS,MAAM,EAAE,EAC5B,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,KACtD,MAYF,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GAAI,eAAe,MAAM,KAAG,SAAS,MAAM,EAC7B,CAAA;AAmBhD;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GACzB,UAAU,SAAS,MAAM,EAAE,EAC3B,yBAAyB,SAAS,MAAM,EAAE,KACzC,SAAS,MAAM,EAMjB,CAAA;AAKD;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,GAAI,aAAa,OAAO,KAAG,OACV,CAAA;AAE/C;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,aAAa,OAAO,KAAG,OACT,CAAA;AAE/C;;;;;;;;;GASG;AACH,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,CAAA;AAE9D;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,aAAa,OAAO,KAAG,UAKnD,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,UAAU,GAAI,MAAM,UAAU,KAAG,OAA6B,CAAA;AAE3E;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,GAAI,UAAU,SAAS,eAAe,EAAE,KAAG,SAAS,MAAM,EAKzF,CAAA;AAYD;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,GACjC,UAAU,SAAS,eAAe,EAAE,EACpC,SAAS,SAAS,WAAW,EAAE,KAC9B,SAAS,MAAM,EAeb,CAAA;AAEL;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,GAAI,SAAS,SAAS,WAAW,EAAE,KAAG,SAAS,MAAM,EAMtF,CAAA;AAEP,mEAAmE;AACnE,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,uBAAuB,GAAI,SAAS,SAAS,YAAY,EAAE,KAAG,SAAS,MAAM,EAYtF,CAAA;AAEJ;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,kBAAkB,GAAI,cAAc,SAAS,MAAM,EAAE,KAAG,SAAS,MAAM,EACpB,CAAA;AAEhE,gFAAgF;AAChF,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,QAAQ,CAAA;AAE5C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,eAAe,GAC1B,cAAc,SAAS,MAAM,EAAE,EAC/B,MAAM,aAAa,KAClB,SAAS,MAAM,EAOjB,CAAA"}
@@ -0,0 +1,228 @@
1
+ // What a repository is made of: where its root is, which directories are pnpm projects, and which of
2
+ // those ploaness governs.
3
+ //
4
+ // ploaness governed exactly one package until now - `process.cwd()`, with every path joined onto it. That
5
+ // was not a simplification of workspaces, it was blindness to them: run from a member, the gates read the
6
+ // member's directory for files that only exist at the workspace root, and two of them reported a verdict
7
+ // about a file they had not found. This module is the missing distinction, kept pure so the rules about
8
+ // which directories are governed can be spec'd against directory lists no filesystem has to produce.
9
+ import { matchesGlob } from './file-roles.js';
10
+ import { declaredDependencies } from './json-shapes.js';
11
+ import { HARNESS_PACKAGE, NEXT_PACKAGE, PAYLOAD_PACKAGE } from './version-policy.js';
12
+ import { topLevelListItems } from './yaml-blocks.js';
13
+ /** The repo-relative path of the member that sits at the repository root. */
14
+ export const ROOT_MEMBER_PATH = '.';
15
+ /** The workspace file key naming the directories pnpm treats as projects. */
16
+ const PACKAGES_KEY = 'packages';
17
+ /**
18
+ * Locate the repository root from the directories above the working directory.
19
+ *
20
+ * Deliberately not `git rev-parse --show-toplevel`. The integration fixtures run cases in directories
21
+ * that are never `git init`-ed, and asking git there walks out of the fixture and adopts whichever
22
+ * repository happens to contain the temporary directory - so a case would be judged against a tree
23
+ * nobody wrote. A workspace file is the stronger signal anyway: it is what pnpm itself reads to decide
24
+ * where the workspace begins.
25
+ * @param ancestors the working directory first, then each parent up to the filesystem root.
26
+ * @param hasEntry whether a directory holds a named entry.
27
+ * @returns the repository root, falling back to the working directory when nothing above it qualifies.
28
+ */
29
+ export const findRepositoryRoot = (ancestors, hasEntry) => {
30
+ const workspace = ancestors.find((directory) => hasEntry(directory, 'pnpm-workspace.yaml'));
31
+ if (workspace !== undefined) {
32
+ return workspace;
33
+ }
34
+ const checkout = ancestors.find((directory) => hasEntry(directory, '.git') && hasEntry(directory, 'package.json'));
35
+ return checkout ?? ancestors[0] ?? ROOT_MEMBER_PATH;
36
+ };
37
+ /**
38
+ * The directory patterns a workspace file declares.
39
+ * @param workspaceFile the contents of pnpm-workspace.yaml, or an empty string when absent.
40
+ * @returns one pattern per entry, in declaration order, including any leading-`!` exclusions.
41
+ */
42
+ export const readWorkspacePackages = (workspaceFile) => topLevelListItems(workspaceFile, PACKAGES_KEY);
43
+ const EXCLUSION_MARKER = '!';
44
+ // pnpm applies every exclusion after every inclusion rather than in written order, so a directory named
45
+ // by both is excluded whichever came first. Reading them in order would make the result depend on how
46
+ // the file happens to be arranged.
47
+ const isSelected = (patterns, directory) => {
48
+ const isIncluded = patterns
49
+ .filter((pattern) => !pattern.startsWith(EXCLUSION_MARKER))
50
+ .some((pattern) => matchesGlob(pattern, directory));
51
+ const isExcluded = patterns
52
+ .filter((pattern) => pattern.startsWith(EXCLUSION_MARKER))
53
+ .some((pattern) => matchesGlob(pattern.slice(EXCLUSION_MARKER.length), directory));
54
+ return isIncluded && !isExcluded;
55
+ };
56
+ /**
57
+ * The directories a workspace file selects as pnpm projects.
58
+ *
59
+ * The repository root is always a project whether or not `packages:` names it: it carries the manifest
60
+ * that declares the package manager, the engines and the scripts a run is invoked through, so a
61
+ * repository whose root were not a project would have no place to state any of them.
62
+ * @param patterns the declared directory patterns.
63
+ * @param directoriesWithManifest every repo-relative directory holding a tracked package.json.
64
+ * @returns the selected directories, root first, without duplicates.
65
+ */
66
+ export const selectProjects = (patterns, directoriesWithManifest) => [
67
+ ...(directoriesWithManifest.includes(ROOT_MEMBER_PATH) ? [ROOT_MEMBER_PATH] : []),
68
+ ...directoriesWithManifest.filter((directory) => directory !== ROOT_MEMBER_PATH && isSelected(patterns, directory)),
69
+ ];
70
+ const declaresPackage = (packageJson, packageName) => Object.hasOwn(declaredDependencies(packageJson), packageName);
71
+ /**
72
+ * Whether a project's manifest declares ploaness, which is what makes it a governed member.
73
+ *
74
+ * The marker is not a switch a project flips to escape. Under pnpm's strict layout a package that does
75
+ * not depend on ploaness cannot resolve `ploaness/biome`, `ploaness/eslint` or `ploaness/tsconfig.json`
76
+ * from its own configuration files, so its wiring could not work whatever this rule said - and dropping
77
+ * the declaration to avoid the gates is reported by {@link findUngovernedProjects} rather than obeyed.
78
+ * @param packageJson the project's parsed manifest.
79
+ * @returns true when ploaness is a declared dependency.
80
+ */
81
+ export const isGovernedProject = (packageJson) => declaresPackage(packageJson, HARNESS_PACKAGE);
82
+ /**
83
+ * Whether a project's manifest declares Payload, which decides the third scope.
84
+ * @param packageJson the project's parsed manifest.
85
+ * @returns true when payload is a declared dependency.
86
+ */
87
+ export const isPayloadProject = (packageJson) => declaresPackage(packageJson, PAYLOAD_PACKAGE);
88
+ /**
89
+ * Decide a member's kind from its manifest.
90
+ * @param packageJson the member's parsed manifest.
91
+ * @returns `payload` when it declares Payload, `application` when it declares Next, else `library`.
92
+ */
93
+ export const memberKindOf = (packageJson) => {
94
+ if (declaresPackage(packageJson, PAYLOAD_PACKAGE)) {
95
+ return 'payload';
96
+ }
97
+ return declaresPackage(packageJson, NEXT_PACKAGE) ? 'application' : 'library';
98
+ };
99
+ /**
100
+ * Whether a member has a server of its own, and therefore a build, a client bundle and a browser to
101
+ * drive. A library has none of the three, so the gates and configurations about them do not apply to it.
102
+ * @param kind the member's kind.
103
+ * @returns true for a Payload or Next application.
104
+ */
105
+ export const hasRuntime = (kind) => kind !== 'library';
106
+ /**
107
+ * The projects ploaness governs.
108
+ *
109
+ * A repository where nothing declares ploaness still has one member - its root - so a project that has
110
+ * not been initialised yet is reported by `preflight` and `wiring` rather than by an empty run that
111
+ * would look like a pass.
112
+ * @param projects every pnpm project the workspace selects.
113
+ * @returns the governed member paths, or the root alone when none qualifies.
114
+ */
115
+ export const findGovernedMembers = (projects) => {
116
+ const governed = projects
117
+ .filter((project) => isGovernedProject(project.packageJson))
118
+ .map((project) => project.path);
119
+ return governed.length > 0 ? governed : [ROOT_MEMBER_PATH];
120
+ };
121
+ // A project inside a member's declared source roots is already judged as that member's source. Treating
122
+ // it as ungoverned would report every fixture and example directory a governed package legitimately
123
+ // carries.
124
+ const isCoveredBy = (member, projectPath) => member.sourceRoots.some((root) => projectPath.startsWith(member.path === ROOT_MEMBER_PATH ? `${root}/` : `${member.path}/${root}/`));
125
+ /**
126
+ * Report every pnpm project ploaness is not judging.
127
+ *
128
+ * This is the anti-bypass half of the member marker. Dropping `ploaness` from a package's manifest
129
+ * removes it from the governed set, which without this rule would be a silent way to take a whole
130
+ * application out of verification.
131
+ * @param projects every pnpm project the workspace selects.
132
+ * @param members the governed members.
133
+ * @returns one finding per project that is neither governed nor inside a governed member's source.
134
+ */
135
+ export const findUngovernedProjects = (projects, members) => projects
136
+ .filter((project) => {
137
+ const isMember = members.some((member) => member.path === project.path);
138
+ return !(isMember ||
139
+ members.some((member) => isCoveredBy(member, project.path)));
140
+ })
141
+ .map((project) => `${project.path}/package.json: is a pnpm project ploaness does not govern; ` +
142
+ `declare "${HARNESS_PACKAGE}" so its source is verified, or remove the package`);
143
+ /**
144
+ * Verify the repository is a Payload project.
145
+ *
146
+ * Asked of the repository rather than of one directory, because a workspace legitimately holds packages
147
+ * that are not Payload applications. What ploaness refuses to judge is a repository with no Payload in
148
+ * it at all, which is the same refusal `preflight` always made, moved up one level.
149
+ * @param members the governed members.
150
+ * @returns a single finding when no member declares payload.
151
+ */
152
+ export const findPayloadMemberViolations = (members) => members.some((member) => member.isPayload)
153
+ ? []
154
+ : [
155
+ 'no package in this repository declares "payload"; ploaness governs Payload CMS projects ' +
156
+ 'and will not judge another kind',
157
+ ];
158
+ /**
159
+ * Report members that would drive the same origin.
160
+ *
161
+ * The end-to-end configuration starts the application and reuses an existing server when one already
162
+ * answers. Two members sharing an origin therefore do not collide loudly: the second run finds the first
163
+ * application still listening, sweeps it, and passes - reporting on an application that was never under
164
+ * test. Nothing else in the harness can see that, and it exists only once more than one member has a
165
+ * browser suite.
166
+ * @param origins each member's path and declared origin.
167
+ * @returns one finding per member sharing an origin with an earlier one.
168
+ */
169
+ export const findServerUrlCollisions = (origins) => origins.flatMap((origin, index) => {
170
+ const earlier = origins
171
+ .slice(0, index)
172
+ .find((other) => other.serverUrl === origin.serverUrl);
173
+ return earlier === undefined
174
+ ? []
175
+ : [
176
+ `${origin.path}/package.json ploaness.serverUrl: is "${origin.serverUrl}", which ` +
177
+ `${earlier.path} already drives; the end-to-end run would reuse that application's ` +
178
+ 'server and sweep the wrong one',
179
+ ];
180
+ });
181
+ /**
182
+ * The patterns that stop an analyzer walking below this member into another member.
183
+ *
184
+ * A tool that reads everything under the directory it runs in has no idea where a package ends, and at a
185
+ * workspace root every sibling sits underneath. knip reported each sibling's entry point as an unused
186
+ * file that way; ESLint reported findings no member's own gate reports, because a shipped configuration
187
+ * reads its settings from the working directory - so the framework glue and the generated artefacts a
188
+ * member declares are absent from the configuration a root-started run renders, whichever config file
189
+ * that run resolves. Nothing is lost by stopping: each sibling is a separate analysis, under its own
190
+ * settings and its own entry points, which is what running the gate per member does.
191
+ *
192
+ * Shared rather than spelled out at each call site, because a boundary two analyzers disagree about is
193
+ * one analyzer reaching past it.
194
+ * @param siblingPaths the other members' paths, relative to this member's directory.
195
+ * @returns one recursive ignore pattern per sibling.
196
+ */
197
+ export const analysisBoundaries = (siblingPaths) => siblingPaths.map((sibling) => `${sibling}/**`);
198
+ /**
199
+ * The arguments an ESLint run takes, whether it is judging a member or writing to one.
200
+ *
201
+ * The boundary has to ride on the command line rather than sit in a configuration file, because ESLint
202
+ * resolves its ignores from the working directory: a run started at a workspace root renders the ROOT's
203
+ * settings over a member's files, so the framework glue and generated artefacts that member declares are
204
+ * simply absent from what that run sees. `analysisBoundaries` states where to stop; this states that
205
+ * both runs are told.
206
+ *
207
+ * They were not. The gate carried the boundary and `ploaness format` did not, which is the worse of the
208
+ * two halves to get wrong: a judge reaching too far reports a finding somebody argues with, while a
209
+ * WRITER reaching too far edits files no gate will ever look at. A root-started format rewrote a Payload
210
+ * member's generated import map - stripping the `@type` annotation and rewriting an import - and every
211
+ * gate in the repository stayed green, because each member's own run correctly excludes that file.
212
+ *
213
+ * The mode flag is the only legitimate difference. `--max-warnings=0` is a verdict, which a run that
214
+ * writes does not render; `ploaness format` deliberately succeeds while findings remain, since the
215
+ * unfixable ones are for `ploaness verify` to report.
216
+ * @param siblingPaths the other members' paths, relative to this member's directory.
217
+ * @param mode whether this run writes fixes or renders a verdict.
218
+ * @returns the full argument list, the target first.
219
+ */
220
+ export const eslintArguments = (siblingPaths, mode) => [
221
+ '.',
222
+ mode === 'fix' ? '--fix' : '--max-warnings=0',
223
+ ...analysisBoundaries(siblingPaths).flatMap((pattern) => [
224
+ '--ignore-pattern',
225
+ pattern,
226
+ ]),
227
+ ];
228
+ //# sourceMappingURL=workspace-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-policy.js","sourceRoot":"","sources":["../src/workspace-policy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Whether an install config declares a top-level key at all.
3
+ *
4
+ * Separate from reading the block because a key can be meaningful while its body is empty:
5
+ * `onlyBuiltDependencies: []` permits no dependency to run a script, which is an answer rather than
6
+ * an omission.
7
+ * @param file the contents of a YAML file, or an empty string when it is absent.
8
+ * @param key the top-level key to look for.
9
+ * @returns true when the key appears unindented.
10
+ */
11
+ export declare const declaresTopLevelKey: (file: string, key: string) => boolean;
12
+ /**
13
+ * The lines of one top-level block, which run until the next unindented line.
14
+ * @param file the contents of a YAML file, or an empty string when it is absent.
15
+ * @param key the top-level key whose body to read.
16
+ * @returns the block's lines, or an empty array when the key is absent.
17
+ */
18
+ export declare const topLevelBlockLines: (file: string, key: string) => readonly string[];
19
+ /**
20
+ * Strip one layer of matching or unmatched surrounding quotes.
21
+ * @param value the raw scalar.
22
+ * @returns the value without its surrounding quote characters.
23
+ */
24
+ export declare const withoutQuotes: (value: string) => string;
25
+ /**
26
+ * The items of a top-level YAML sequence, such as the `packages:` block of a workspace file.
27
+ * @param file the contents of a YAML file, or an empty string when it is absent.
28
+ * @param key the top-level key whose sequence to read.
29
+ * @returns one unquoted entry per `- item` line, in declaration order.
30
+ */
31
+ export declare const topLevelListItems: (file: string, key: string) => readonly string[];
32
+ /**
33
+ * The `key: value` entries of a top-level YAML mapping, such as an `overrides:` block.
34
+ *
35
+ * Split at the FIRST colon, not the last: a value carries colons of its own - `npm:preact@10`,
36
+ * `link:../fork`, `git+ssh://...` - and splitting at the last one read `react: npm:preact@10` as a
37
+ * package called "react: npm", so every alias form walked straight through the rule meant to catch it.
38
+ * @param file the contents of a YAML file, or an empty string when it is absent.
39
+ * @param key the top-level key whose mapping to read.
40
+ * @returns one `[name, value]` pair per entry, both unquoted.
41
+ */
42
+ export declare const topLevelMappingEntries: (file: string, key: string) => readonly (readonly [string, string])[];
43
+ //# sourceMappingURL=yaml-blocks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"yaml-blocks.d.ts","sourceRoot":"","sources":["../src/yaml-blocks.ts"],"names":[],"mappings":"AAeA;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,KAAG,OACY,CAAA;AAE5E;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,KAAG,SAAS,MAAM,EAW7E,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,OAAO,MAAM,KAAG,MAA8C,CAAA;AAe5F;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,KAAG,SAAS,MAAM,EAIpB,CAAA;AAEzD;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,GACjC,MAAM,MAAM,EACZ,KAAK,MAAM,KACV,SAAS,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAcpC,CAAA"}
@@ -0,0 +1,87 @@
1
+ // The small reader for the top-level blocks pnpm reads out of a workspace file.
2
+ //
3
+ // It is not a YAML parser and must not become one. pnpm honours these keys only at the top level of
4
+ // `pnpm-workspace.yaml`, so what has to be recognised is exactly "an unindented key, and the indented
5
+ // lines beneath it" - a shape a regex reads correctly and a parser would only read more expensively.
6
+ //
7
+ // It lives here rather than in `install-policy.ts`, which wrote it first, because `workspace-policy.ts`
8
+ // needs the same three predicates to read the `packages:` block. Two crude readers in one package would
9
+ // not stay equal, and the one that drifted would be the one deciding which directories are governed.
10
+ const isTopLevelKey = (line, key) => new RegExp(String.raw `^${key}\s*:`).test(line);
11
+ const isIndented = (line) => /^\s+\S/.test(line);
12
+ /**
13
+ * Whether an install config declares a top-level key at all.
14
+ *
15
+ * Separate from reading the block because a key can be meaningful while its body is empty:
16
+ * `onlyBuiltDependencies: []` permits no dependency to run a script, which is an answer rather than
17
+ * an omission.
18
+ * @param file the contents of a YAML file, or an empty string when it is absent.
19
+ * @param key the top-level key to look for.
20
+ * @returns true when the key appears unindented.
21
+ */
22
+ export const declaresTopLevelKey = (file, key) => file.split('\n').some((line) => isTopLevelKey(line, key));
23
+ /**
24
+ * The lines of one top-level block, which run until the next unindented line.
25
+ * @param file the contents of a YAML file, or an empty string when it is absent.
26
+ * @param key the top-level key whose body to read.
27
+ * @returns the block's lines, or an empty array when the key is absent.
28
+ */
29
+ export const topLevelBlockLines = (file, key) => {
30
+ const lines = file.split('\n');
31
+ const start = lines.findIndex((line) => isTopLevelKey(line, key));
32
+ if (start === -1) {
33
+ return [];
34
+ }
35
+ const rest = lines.slice(start + 1);
36
+ const end = rest.findIndex((line) => line.trim().length > 0 && !isIndented(line));
37
+ return end === -1 ? rest : rest.slice(0, end);
38
+ };
39
+ /**
40
+ * Strip one layer of matching or unmatched surrounding quotes.
41
+ * @param value the raw scalar.
42
+ * @returns the value without its surrounding quote characters.
43
+ */
44
+ export const withoutQuotes = (value) => value.replaceAll(/^['"]|['"]$/g, '');
45
+ // A whole-line comment is not content. Trailing text after a `#` is stripped only where it cannot be
46
+ // part of a value: a path glob carries no `#`, but an override specifier does - `git+ssh://host/repo#tag`
47
+ // is a legal pin, and cutting at the `#` would silently rewrite which commit the rule judges.
48
+ const contentLines = (lines) => lines
49
+ .map((line) => line.trim())
50
+ .filter((line) => line.length > 0 && !line.startsWith('#'));
51
+ const withoutTrailingComment = (line) => {
52
+ const hash = line.indexOf('#');
53
+ return hash === -1 ? line : line.slice(0, hash).trim();
54
+ };
55
+ /**
56
+ * The items of a top-level YAML sequence, such as the `packages:` block of a workspace file.
57
+ * @param file the contents of a YAML file, or an empty string when it is absent.
58
+ * @param key the top-level key whose sequence to read.
59
+ * @returns one unquoted entry per `- item` line, in declaration order.
60
+ */
61
+ export const topLevelListItems = (file, key) => contentLines(topLevelBlockLines(file, key))
62
+ .filter((line) => line.startsWith('-'))
63
+ .map((line) => withoutQuotes(withoutTrailingComment(line.slice(1).trim())))
64
+ .filter((entry) => entry.length > 0);
65
+ /**
66
+ * The `key: value` entries of a top-level YAML mapping, such as an `overrides:` block.
67
+ *
68
+ * Split at the FIRST colon, not the last: a value carries colons of its own - `npm:preact@10`,
69
+ * `link:../fork`, `git+ssh://...` - and splitting at the last one read `react: npm:preact@10` as a
70
+ * package called "react: npm", so every alias form walked straight through the rule meant to catch it.
71
+ * @param file the contents of a YAML file, or an empty string when it is absent.
72
+ * @param key the top-level key whose mapping to read.
73
+ * @returns one `[name, value]` pair per entry, both unquoted.
74
+ */
75
+ export const topLevelMappingEntries = (file, key) => contentLines(topLevelBlockLines(file, key)).flatMap((line) => {
76
+ const separator = line.indexOf(':');
77
+ if (separator === -1) {
78
+ return [];
79
+ }
80
+ return [
81
+ [
82
+ withoutQuotes(line.slice(0, separator).trim()),
83
+ withoutQuotes(line.slice(separator + 1).trim()),
84
+ ],
85
+ ];
86
+ });
87
+ //# sourceMappingURL=yaml-blocks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"yaml-blocks.js","sourceRoot":"","sources":["../src/yaml-blocks.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,EAAE;AACF,oGAAoG;AACpG,sGAAsG;AACtG,qGAAqG;AACrG,EAAE;AACF,wGAAwG;AACxG,wGAAwG;AACxG,qGAAqG;AAErG,MAAM,aAAa,GAAG,CAAC,IAAY,EAAE,GAAW,EAAW,EAAE,CAC3D,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,CAAA,IAAI,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAEhD,MAAM,UAAU,GAAG,CAAC,IAAY,EAAW,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAEjE;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,IAAY,EAAE,GAAW,EAAW,EAAE,CACxE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,IAAY,EAAW,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAA;AAE5E;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,IAAY,EAAE,GAAW,EAAqB,EAAE;IACjF,MAAM,KAAK,GAAsB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACjD,MAAM,KAAK,GAAW,KAAK,CAAC,SAAS,CAAC,CAAC,IAAY,EAAW,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAA;IAC1F,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;QACjB,OAAO,EAAE,CAAA;IACX,CAAC;IACD,MAAM,IAAI,GAAsB,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAA;IACtD,MAAM,GAAG,GAAW,IAAI,CAAC,SAAS,CAChC,CAAC,IAAY,EAAW,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CACvE,CAAA;IACD,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;AAC/C,CAAC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,cAAc,EAAE,EAAE,CAAC,CAAA;AAE5F,qGAAqG;AACrG,0GAA0G;AAC1G,8FAA8F;AAC9F,MAAM,YAAY,GAAG,CAAC,KAAwB,EAAqB,EAAE,CACnE,KAAK;KACF,GAAG,CAAC,CAAC,IAAY,EAAU,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;KAC1C,MAAM,CAAC,CAAC,IAAY,EAAW,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAA;AAEhF,MAAM,sBAAsB,GAAG,CAAC,IAAY,EAAU,EAAE;IACtD,MAAM,IAAI,GAAW,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IACtC,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,EAAE,CAAA;AACxD,CAAC,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,IAAY,EAAE,GAAW,EAAqB,EAAE,CAChF,YAAY,CAAC,kBAAkB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;KACxC,MAAM,CAAC,CAAC,IAAY,EAAW,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;KACvD,GAAG,CAAC,CAAC,IAAY,EAAU,EAAE,CAAC,aAAa,CAAC,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;KAC1F,MAAM,CAAC,CAAC,KAAa,EAAW,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;AAEzD;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CACpC,IAAY,EACZ,GAAW,EAC6B,EAAE,CAC1C,YAAY,CAAC,kBAAkB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,CACjD,CAAC,IAAY,EAA0C,EAAE;IACvD,MAAM,SAAS,GAAW,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAC3C,IAAI,SAAS,KAAK,CAAC,CAAC,EAAE,CAAC;QACrB,OAAO,EAAE,CAAA;IACX,CAAC;IACD,OAAO;QACL;YACE,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC;YAC9C,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SAChD;KACF,CAAA;AACH,CAAC,CACF,CAAA"}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@ploaness/governance",
3
+ "version": "1.0.0",
4
+ "description": "Pure, unit-tested governance rules for ploaness. No filesystem, process, or network access.",
5
+ "license": "MIT",
6
+ "author": "Herman Ciechanowiec <herman@ciechanowiec.eu>",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/ciechanowiec/ploaness.git",
10
+ "directory": "packages/governance"
11
+ },
12
+ "homepage": "https://github.com/ciechanowiec/ploaness#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/ciechanowiec/ploaness/issues"
15
+ },
16
+ "type": "module",
17
+ "main": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js"
23
+ }
24
+ },
25
+ "files": [
26
+ "dist"
27
+ ],
28
+ "engines": {
29
+ "node": ">=26"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "26.4.0",
36
+ "typescript": "6.0.3"
37
+ },
38
+ "scripts": {
39
+ "build": "tsc --build"
40
+ }
41
+ }