workspace-conformance 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -99,6 +99,23 @@ function expandTemplate(template, values) {
99
99
  return value;
100
100
  });
101
101
  }
102
+ const NAME_PLACEHOLDER = "{name}";
103
+ /**
104
+ * What a name template must be, as a configuration error says it. It does not quote the template, as no configuration error quotes a value.
105
+ */
106
+ const NAME_TEMPLATE_REQUIREMENT = "needs {name} exactly once, other text beside it and no other placeholder";
107
+ const ANY_PLACEHOLDER = /\{[a-z]+\}/u;
108
+ /**
109
+ * `template` split at `{name}`, or `undefined` unless it holds `{name}` exactly once, other text beside it and no other placeholder. Without other text every name would match, and with `{name}` twice a name could be split more than one way.
110
+ */
111
+ function parseNameTemplate(template) {
112
+ const [before, after, ...rest] = template.split(NAME_PLACEHOLDER);
113
+ if (before === void 0 || after === void 0 || rest.length > 0 || before + after === "" || ANY_PLACEHOLDER.test(before + after)) return;
114
+ return {
115
+ before,
116
+ after
117
+ };
118
+ }
102
119
  //#endregion
103
120
  //#region src/ts-project.ts
104
121
  /**
@@ -212,6 +229,97 @@ const aggregateMappers = async ({ cwd, options }) => {
212
229
  return violations.sort((a, b) => a.file.localeCompare(b.file) || (a.location?.line ?? 0) - (b.location?.line ?? 0) || a.message.localeCompare(b.message));
213
230
  };
214
231
  //#endregion
232
+ //#region src/checks/codec-pairs.ts
233
+ function collect(container, prefix, found, entered) {
234
+ for (const [name, declarations] of container.getExportedDeclarations()) {
235
+ const value = declarations.find((declaration) => !Node.isInterfaceDeclaration(declaration) && !Node.isTypeAliasDeclaration(declaration) && !Node.isModuleDeclaration(declaration) && !Node.isSourceFile(declaration));
236
+ if (value !== void 0) found.set(`${prefix}${name}`, value);
237
+ for (const member of declarations) if (Node.isModuleDeclaration(member)) collect(member, `${prefix}${name}.`, found, entered);
238
+ else if (Node.isSourceFile(member) && !entered.has(member)) collect(member, `${prefix}${name}.`, found, /* @__PURE__ */ new Set([...entered, member]));
239
+ }
240
+ }
241
+ /**
242
+ * The values `file` exports, by name, wherever they are declared: a re-export counts. Those inside an exported namespace, or in a module re-exported as a namespace (`export * as ns from './x'`), are included under their qualified name (`Namespace.value`). Interfaces and type aliases are types, not codecs, and a namespace is where codecs live rather than one itself, so all three are left out.
243
+ */
244
+ function exportedValues(file) {
245
+ const found = /* @__PURE__ */ new Map();
246
+ collect(file, "", found, /* @__PURE__ */ new Set([file]));
247
+ return found;
248
+ }
249
+ /**
250
+ * Where the declaration's name is written, or where the declaration starts when it has no name node.
251
+ */
252
+ function valueLocation(declaration) {
253
+ const nameNode = Node.hasName(declaration) ? declaration.getNameNode() : void 0;
254
+ const { line, column } = declaration.getSourceFile().getLineAndColumnAtPos((nameNode ?? declaration).getStart());
255
+ return {
256
+ line,
257
+ column
258
+ };
259
+ }
260
+ /**
261
+ * The template of an option, which the configuration has already validated; a template that does not parse is a configuration error naming the option.
262
+ */
263
+ function templateOf(options, option) {
264
+ const template = parseNameTemplate(options[option]);
265
+ if (template === void 0) throw new ConformanceError(`checks.codec-pairs.${option}: ${NAME_TEMPLATE_REQUIREMENT}`);
266
+ return template;
267
+ }
268
+ /**
269
+ * The shared name a qualified export name gives under `template`, or `undefined` when its last segment does not fit the template. The namespace part is kept apart so a counterpart is looked for in the same namespace.
270
+ */
271
+ function sharedName(qualified, template) {
272
+ const namespace = qualified.slice(0, qualified.lastIndexOf(".") + 1);
273
+ const local = qualified.slice(namespace.length);
274
+ if (local.length <= template.before.length + template.after.length || !local.startsWith(template.before) || !local.endsWith(template.after)) return;
275
+ return {
276
+ namespace,
277
+ name: local.slice(template.before.length, local.length - template.after.length)
278
+ };
279
+ }
280
+ /**
281
+ * Every encoder a codec file exports has the decoder its name implies, exported by the same file, and every decoder the encoder. A name is an encoder or a decoder when it fits that template; other exports are not codecs and are not judged. A half re-exported from another file counts, and is reported where it is declared.
282
+ *
283
+ * It checks that the counterpart is exported, not that the two round-trip: that is a property test of the repository's own.
284
+ */
285
+ const codecPairs = async ({ cwd, options }) => {
286
+ const { project, files } = await createProject(cwd, options.tsConfig, options.codecs, {
287
+ check: "codec-pairs",
288
+ files: "codecs"
289
+ });
290
+ const excluded = new Set(options.exclude);
291
+ const encoder = templateOf(options, "encoder");
292
+ const decoder = templateOf(options, "decoder");
293
+ const halves = [{
294
+ template: encoder,
295
+ counterpart: decoder,
296
+ missing: "decoder"
297
+ }, {
298
+ template: decoder,
299
+ counterpart: encoder,
300
+ missing: "encoder"
301
+ }];
302
+ const violations = [];
303
+ for (const file of files) {
304
+ const values = exportedValues(project.getSourceFileOrThrow(resolve(cwd, file)));
305
+ for (const [qualified, declaration] of values) {
306
+ if (excluded.has(qualified)) continue;
307
+ for (const { template, counterpart, missing } of halves) {
308
+ const shared = sharedName(qualified, template);
309
+ if (shared === void 0) continue;
310
+ const expected = `${shared.namespace}${counterpart.before}${shared.name}${counterpart.after}`;
311
+ if (!values.has(expected)) violations.push({
312
+ code: `codec-pairs/missing-${missing}`,
313
+ message: `${qualified} has no ${missing}: ${file} does not export ${expected}`,
314
+ file: relativePosix(cwd, declaration.getSourceFile().getFilePath()),
315
+ location: valueLocation(declaration)
316
+ });
317
+ }
318
+ }
319
+ }
320
+ return violations.sort((a, b) => a.file.localeCompare(b.file) || (a.location?.line ?? 0) - (b.location?.line ?? 0) || a.message.localeCompare(b.message));
321
+ };
322
+ //#endregion
215
323
  //#region src/checks/type-references.ts
216
324
  /**
217
325
  * The identifier a reference ends in: `infer` in `z.infer`, the name itself when it is not qualified.
@@ -2019,6 +2127,9 @@ function runsOnOf(reader, value, what) {
2019
2127
  group
2020
2128
  };
2021
2129
  }
2130
+ function environmentOf(reader, value, what) {
2131
+ return isRecord(value) ? reader.text(value["name"], `${what}.name`) : reader.text(value, what);
2132
+ }
2022
2133
  function stepOf(reader, value, jobPath, index) {
2023
2134
  const path = [
2024
2135
  ...jobPath,
@@ -2051,6 +2162,7 @@ function jobOf(reader, id, value) {
2051
2162
  needs: reader.list(job["needs"], `${what}.needs`),
2052
2163
  condition: reader.text(job["if"], `${what}.if`),
2053
2164
  permissions: permissionsOf(reader, job["permissions"], `${what}.permissions`),
2165
+ environment: environmentOf(reader, job["environment"], `${what}.environment`),
2054
2166
  env: reader.texts(job["env"], `${what}.env`),
2055
2167
  runsOn: runsOnOf(reader, job["runs-on"], `${what}.runs-on`),
2056
2168
  timeoutMinutes: reader.text(job["timeout-minutes"], `${what}.timeout-minutes`),
@@ -2086,6 +2198,7 @@ function parseWorkflow(file, source) {
2086
2198
  triggers,
2087
2199
  triggerLocations: triggerLocationsOf(reader, triggers, root["on"]),
2088
2200
  permissions: permissionsOf(reader, root["permissions"], "permissions"),
2201
+ permissionsLocation: root["permissions"] === void 0 ? void 0 : reader.location(["permissions"]),
2089
2202
  env: reader.texts(root["env"], "env"),
2090
2203
  jobs: Object.entries(reader.record(root["jobs"], "jobs")).map(([id, job]) => jobOf(reader, id, job)),
2091
2204
  callOutputs: Object.fromEntries(Object.entries(outputs).map(([name, output]) => [name, reader.text(reader.record(output, `on.workflow_call.outputs.${name}`)["value"], `on.workflow_call.outputs.${name}.value`) ?? ""]))
@@ -2264,6 +2377,14 @@ function callsAlways(condition) {
2264
2377
  * The default ids of junction jobs, shared by the checks that look for one.
2265
2378
  */
2266
2379
  const DEFAULT_JUNCTION_JOBS = ["required-checks"];
2380
+ /**
2381
+ * The default ids of release jobs, shared by the checks that look for one.
2382
+ */
2383
+ const DEFAULT_RELEASE_JOBS = ["release"];
2384
+ /**
2385
+ * The variables a token for the npm registry is passed in.
2386
+ */
2387
+ const REGISTRY_TOKEN_VARIABLES = ["NODE_AUTH_TOKEN", "NPM_TOKEN"];
2267
2388
  //#endregion
2268
2389
  //#region src/checks/settings-required-checks.ts
2269
2390
  /**
@@ -2414,10 +2535,6 @@ const PACKAGE_MANAGER_OPTIONS = String.raw`(?:\s+(?:(?:--filter|-F|--workspace|-
2414
2535
  * Commands that publish a package to a registry.
2415
2536
  */
2416
2537
  const PUBLISH_COMMAND = new RegExp(String.raw`^(?:(?:npm|pnpm|yarn)${PACKAGE_MANAGER_OPTIONS}(?:\s+npm)?\s+publish|(?:(?:pnpm|yarn)(?:\s+exec)?\s+|npx\s+)?(?:semantic-release|changeset\s+publish|lerna\s+publish))(?:\s|$)`, "u");
2417
- /**
2418
- * The variables a token for the registry is passed in.
2419
- */
2420
- const TOKEN_VARIABLES = ["NODE_AUTH_TOKEN", "NPM_TOKEN"];
2421
2538
  const PROVENANCE_FLAG = /--provenance(?:=true)?(?:\s|$)/u;
2422
2539
  /**
2423
2540
  * Whether the `package.json` in the working directory asks for provenance with `publishConfig.provenance: true`, which applies to every publish of that package.
@@ -2441,7 +2558,7 @@ function attestationViolations(workflow, job) {
2441
2558
  * Whether the job passes a registry token to a step, which makes its publish not tokenless: a token variable with a value other than the empty string, anywhere in the environment.
2442
2559
  */
2443
2560
  function passesToken(workflow, job) {
2444
- return [void 0, ...job.steps].some((step) => TOKEN_VARIABLES.some((variable) => (effectiveEnv(workflow, job, step)[variable] ?? "") !== ""));
2561
+ return [void 0, ...job.steps].some((step) => REGISTRY_TOKEN_VARIABLES.some((variable) => (effectiveEnv(workflow, job, step)[variable] ?? "") !== ""));
2445
2562
  }
2446
2563
  function tokenlessPublishViolations(workflow, job, provenanceInManifest) {
2447
2564
  if (accessOf(effectivePermissions(workflow, job), "id-token") !== "write" || passesToken(workflow, job)) return [];
@@ -2449,9 +2566,9 @@ function tokenlessPublishViolations(workflow, job, provenanceInManifest) {
2449
2566
  const publishes = scriptCommands(step.run).filter((line) => PUBLISH_COMMAND.test(line));
2450
2567
  if (publishes.length === 0) return [];
2451
2568
  const env = effectiveEnv(workflow, job, step);
2452
- const blanked = TOKEN_VARIABLES.every((variable) => env[variable] === "");
2569
+ const blanked = REGISTRY_TOKEN_VARIABLES.every((variable) => env[variable] === "");
2453
2570
  const provenance = provenanceInManifest || publishes.some((line) => PROVENANCE_FLAG.test(line)) || env["NPM_CONFIG_PROVENANCE"] === "true";
2454
- return blanked || provenance ? [] : [workflowViolation(workflow, "workflow-credentials/tokenless-publish-unprotected", `job '${job.id}' publishes with an OIDC identity and no token, but does not set ${TOKEN_VARIABLES.join(" and ")} to the empty string, so a token inherited from the environment is used when the OIDC exchange fails; set them to the empty string to prevent that, or request provenance so that such a publish at least carries a traceable attestation`, step.location)];
2571
+ return blanked || provenance ? [] : [workflowViolation(workflow, "workflow-credentials/tokenless-publish-unprotected", `job '${job.id}' publishes with an OIDC identity and no token, but does not set ${REGISTRY_TOKEN_VARIABLES.join(" and ")} to the empty string, so a token inherited from the environment is used when the OIDC exchange fails; set them to the empty string to prevent that, or request provenance so that such a publish at least carries a traceable attestation`, step.location)];
2455
2572
  });
2456
2573
  }
2457
2574
  /**
@@ -2469,7 +2586,6 @@ const workflowCredentials = async ({ cwd, options }) => {
2469
2586
  };
2470
2587
  //#endregion
2471
2588
  //#region src/checks/workflow-job-ordering.ts
2472
- const DEFAULT_RELEASE_JOBS = ["release"];
2473
2589
  const DEFAULT_DEPLOY_JOBS = ["deploy"];
2474
2590
  const DEFAULT_DOCS_DEPLOY_JOBS = ["docs-deploy"];
2475
2591
  const DEFAULT_BRANCH = "main";
@@ -2617,6 +2733,71 @@ const workflowMergeGroup = async ({ cwd, options }) => (await loadWorkflows(cwd,
2617
2733
  return location === void 0 || workflow.triggers.has("merge_group") ? [] : [workflowViolation(workflow, "workflow-merge-group/missing-trigger", "the workflow runs for pull requests but has no merge_group trigger, so its checks do not report for a pull request in the merge queue", location)];
2618
2734
  });
2619
2735
  //#endregion
2736
+ //#region src/checks/workflow-release-job.ts
2737
+ const CHECKOUT_ACTION$1 = "actions/checkout";
2738
+ /**
2739
+ * A value that reads the `secrets` context inside an expression.
2740
+ */
2741
+ const FROM_SECRET = /\$\{\{[^}]*\bsecrets\./u;
2742
+ /**
2743
+ * The release jobs named for a message: `release job 'a'` or `release jobs 'a' and 'b'`.
2744
+ */
2745
+ function releaseJobsText(releases) {
2746
+ const ids = releases.map((job) => `'${job.id}'`);
2747
+ return `release job${ids.length === 1 ? "" : "s"} ${ids.join(" and ")}`;
2748
+ }
2749
+ function grantsIdToken(permissions) {
2750
+ return accessOf(permissions, "id-token") === "write";
2751
+ }
2752
+ function idTokenViolations(workflow, releases) {
2753
+ const releaseIds = new Set(releases.map((job) => job.id));
2754
+ const named = releaseJobsText(releases);
2755
+ const workflowLevel = grantsIdToken(workflow.permissions) && workflow.permissionsLocation !== void 0 ? [workflowViolation(workflow, "workflow-release-job/id-token-workflow-level", `the workflow's permissions grant id-token: write to every job that declares none of its own; grant it on ${named} only`, workflow.permissionsLocation)] : [];
2756
+ const jobLevel = workflow.jobs.filter((job) => !releaseIds.has(job.id) && grantsIdToken(job.permissions)).map((job) => workflowViolation(workflow, "workflow-release-job/id-token-outside-release", `job '${job.id}' is granted id-token: write but is not a release job, so it can mint the OIDC identity npm trusted publishing accepts for this workflow; only ${named} may hold it`, job.location));
2757
+ return [...workflowLevel, ...jobLevel];
2758
+ }
2759
+ /**
2760
+ * What a checkout that does not set `persist-credentials: false` leaves in git config.
2761
+ */
2762
+ function persistedCredential(step) {
2763
+ if (step.with["ssh-key"] !== void 0) return "the SSH key";
2764
+ return step.with["token"] === void 0 ? "the job token" : "the token it was given";
2765
+ }
2766
+ function releaseJobViolations(workflow, job) {
2767
+ if (job.uses !== void 0) return [];
2768
+ const environment = job.environment === void 0 ? [workflowViolation(workflow, "workflow-release-job/missing-environment", `release job '${job.id}' declares no environment, so nothing outside the workflow file stops a branch's copy of it publishing; run it in a protected environment restricted to the default branch`, job.location)] : [];
2769
+ const checkouts = job.steps.filter((step) => stepUses(step, CHECKOUT_ACTION$1) && step.with["persist-credentials"] !== "false").map((step) => workflowViolation(workflow, "workflow-release-job/persisted-credentials", `release job '${job.id}' checks out without persist-credentials: false, which leaves ${persistedCredential(step)} in git config for every later step`, step.location));
2770
+ return [...environment, ...checkouts];
2771
+ }
2772
+ /**
2773
+ * The token variables an environment sets from a secret.
2774
+ */
2775
+ function secretTokens(env) {
2776
+ return REGISTRY_TOKEN_VARIABLES.filter((variable) => FROM_SECRET.test(env[variable] ?? ""));
2777
+ }
2778
+ function tokenBesideOidcViolations(workflow, job) {
2779
+ if (!grantsIdToken(effectivePermissions(workflow, job))) return [];
2780
+ const report = (variables, location) => variables.length === 0 ? [] : [workflowViolation(workflow, "workflow-release-job/token-beside-oidc", `job '${job.id}' holds id-token: write and passes ${variables.join(" and ")} from a secret, a long-lived npm token beside the OIDC identity; publish through trusted publishing and blank the token variables`, location)];
2781
+ return [...report(secretTokens(effectiveEnv(workflow, job, void 0)), job.location), ...job.steps.flatMap((step) => report(secretTokens(step.env), step.location))];
2782
+ }
2783
+ /**
2784
+ * npm binds a trusted publisher to the repository and the workflow file, not to a branch, so anyone who can push a branch can edit that branch's copy of the workflow, drop an `if` that keeps publishing to the default branch, and publish. What stops that has to live outside the file: a protected environment that only the default branch may deploy to, named in npm's trusted publisher too. This check holds the release job to the shape that makes that protection hold.
2785
+ *
2786
+ * In a workflow with a release job (by id, `releaseJobs`, `release` by default): `workflow-release-job/id-token-workflow-level`, the workflow's own `permissions` grant `id-token: write` (directly or through `write-all`), which every job that declares none inherits; `id-token-outside-release`, a job that is not a release job declares `id-token: write` itself, a job that calls a reusable workflow included; `missing-environment`, a release job declares no `environment`; `persisted-credentials`, a release job runs `actions/checkout` without `persist-credentials: false` (literally; an expression is unknown and reported), so the job token, a `token` input or an `ssh-key` stays in git config for every later step. A release job that pushes as a deploy key provisions the key in a step and hands it to git through that step's `GIT_SSH_COMMAND`, after a checkout that persists nothing, and is accepted as written; semantic-release authenticates its own push (`repositoryUrl` as written, else the URL with `GITHUB_TOKEN` or `GH_TOKEN` from the environment), so it needs nothing from the checkout.
2787
+ *
2788
+ * In every workflow: `token-beside-oidc`, a job whose effective permissions grant `id-token: write` sets `NODE_AUTH_TOKEN` or `NPM_TOKEN` from the `secrets` context in its workflow, job or step `env`.
2789
+ *
2790
+ * Limits: the environment's protection rules and npm's trusted publisher settings are not visible in the file and are not read; a release job that calls a reusable workflow is judged only for `id-token`, since `environment` and steps belong to the called workflow; a token passed through `with`, written to an `.npmrc` by a script, or inherited through a composite action is not seen.
2791
+ */
2792
+ const workflowReleaseJob = async ({ cwd, options }) => {
2793
+ return (await loadWorkflows(cwd, options)).flatMap((workflow) => {
2794
+ const releases = jobsNamed(workflow, options.releaseJobs ?? DEFAULT_RELEASE_JOBS);
2795
+ const releaseFindings = releases.length === 0 ? [] : idTokenViolations(workflow, releases);
2796
+ const jobFindings = workflow.jobs.flatMap((job) => [...releases.includes(job) ? releaseJobViolations(workflow, job) : [], ...tokenBesideOidcViolations(workflow, job)]);
2797
+ return [...releaseFindings, ...jobFindings];
2798
+ });
2799
+ };
2800
+ //#endregion
2620
2801
  //#region src/checks/workflow-repository-dispatch.ts
2621
2802
  const DEFAULT_BRANCHES = ["main", "master"];
2622
2803
  const DEFAULT_BRANCH_EXPRESSION = /\$\{\{\s*github\.event\.repository\.default_branch\s*\}\}/u;
@@ -3070,6 +3251,10 @@ function compiles(source) {
3070
3251
  */
3071
3252
  const pattern = z.string().min(1).refine(compiles, "not a valid regular expression");
3072
3253
  const patterns = z.array(pattern);
3254
+ /**
3255
+ * A name template of `codec-pairs`.
3256
+ */
3257
+ const nameTemplate = z.string().refine((template) => parseNameTemplate(template) !== void 0, NAME_TEMPLATE_REQUIREMENT);
3073
3258
  const severity = z.enum(["warn", "error"]);
3074
3259
  const requiredRules = z.record(z.string().min(1), severity);
3075
3260
  const selection = {
@@ -3102,6 +3287,16 @@ const checks = z.strictObject({
3102
3287
  exclude: z.exactOptional(names),
3103
3288
  tsConfig: z.exactOptional(path)
3104
3289
  }))),
3290
+ "codec-pairs": z.exactOptional(enabled(z.strictObject({
3291
+ codecs: paths,
3292
+ encoder: nameTemplate,
3293
+ decoder: nameTemplate,
3294
+ exclude: z.exactOptional(names),
3295
+ tsConfig: z.exactOptional(path)
3296
+ }).refine((options) => options.encoder !== options.decoder, {
3297
+ path: ["decoder"],
3298
+ message: "the decoder template is the encoder template, so every name would be its own pair"
3299
+ }))),
3105
3300
  "command-types": z.exactOptional(enabled(z.strictObject({
3106
3301
  commands: paths,
3107
3302
  exclude: z.exactOptional(names),
@@ -3179,6 +3374,10 @@ const checks = z.strictObject({
3179
3374
  }))),
3180
3375
  "workflow-version-single-source": z.exactOptional(enabled(z.strictObject(selection))),
3181
3376
  "workflow-credentials": z.exactOptional(enabled(z.strictObject(selection))),
3377
+ "workflow-release-job": z.exactOptional(enabled(z.strictObject({
3378
+ ...selection,
3379
+ releaseJobs: z.exactOptional(names)
3380
+ }))),
3182
3381
  "workflow-repository-dispatch": z.exactOptional(enabled(z.strictObject({
3183
3382
  ...selection,
3184
3383
  defaultBranches: z.exactOptional(names),
@@ -3358,6 +3557,12 @@ const registry = {
3358
3557
  select: (checks) => enabledOptions(checks["aggregate-mappers"]),
3359
3558
  check: aggregateMappers
3360
3559
  }),
3560
+ "codec-pairs": plain({
3561
+ name: "codec-pairs",
3562
+ description: "Every exported encoder in a codec file has a decoder under the naming templates, and every decoder an encoder",
3563
+ select: (checks) => enabledOptions(checks["codec-pairs"]),
3564
+ check: codecPairs
3565
+ }),
3361
3566
  "command-types": plain({
3362
3567
  name: "command-types",
3363
3568
  description: "Every exported command type is derived from a schema, not hand-written",
@@ -3466,6 +3671,12 @@ const registry = {
3466
3671
  select: (checks) => enabledOptions(checks["workflow-credentials"]),
3467
3672
  check: workflowCredentials
3468
3673
  }),
3674
+ "workflow-release-job": plain({
3675
+ name: "workflow-release-job",
3676
+ description: "The release job alone holds id-token, runs in an environment and persists no credentials, and no job with id-token passes an npm token secret",
3677
+ select: (checks) => enabledOptions(checks["workflow-release-job"]),
3678
+ check: workflowReleaseJob
3679
+ }),
3469
3680
  "workflow-repository-dispatch": plain({
3470
3681
  name: "workflow-repository-dispatch",
3471
3682
  description: "A repository_dispatch handler does not push to the default branch, auto-merge unguarded, or rely on the default token",
@@ -3717,4 +3928,4 @@ function proveExhaustive(input) {
3717
3928
  }) };
3718
3929
  }
3719
3930
  //#endregion
3720
- export { ConformanceError, DEFAULT_DERIVED_TYPE_INFERENCES, DEFAULT_INFERENCES, DEFAULT_WORKFLOWS, EXIT_CODES, ExhaustivenessError, PRESET_COMMIT_TYPES, accessOf, aggregateMappers, checkNames, classifyPackages, commandTypes, commitTypes, conformanceSchema, conformanceSection, createGitHubClient, crossSliceRules, cycleRule, deployTools, derivedTypes, dockerfilePackageManager, effectiveEnv, effectivePermissions, eslint, generators, handlerCases, importCrossSlice, importCycles, importIsolatedGroups, importRankSkip, importUphill, instructionSymlinks, isCheckName, isolatedGroupRules, loadWorkflows, migrationsDirectory, packagesPattern, parseUses, parseWorkflow, proveExhaustive, rankSkipRules, registry, removeCase, runChecks, settingsMergeMethods, settingsRequiredChecks, settingsReviewThreadResolution, singleStorybook, transitiveNeeds, typeErrors, uphillRules, workflowActionPinning, workflowCredentials, workflowJobOrdering, workflowMergeGroup, workflowRepositoryDispatch, workflowRunnerResolution, workflowSkippableJobs, workflowUpdateBotCooldown, workflowVersionSingleSource };
3931
+ export { ConformanceError, DEFAULT_DERIVED_TYPE_INFERENCES, DEFAULT_INFERENCES, DEFAULT_WORKFLOWS, EXIT_CODES, ExhaustivenessError, PRESET_COMMIT_TYPES, accessOf, aggregateMappers, checkNames, classifyPackages, codecPairs, commandTypes, commitTypes, conformanceSchema, conformanceSection, createGitHubClient, crossSliceRules, cycleRule, deployTools, derivedTypes, dockerfilePackageManager, effectiveEnv, effectivePermissions, eslint, generators, handlerCases, importCrossSlice, importCycles, importIsolatedGroups, importRankSkip, importUphill, instructionSymlinks, isCheckName, isolatedGroupRules, loadWorkflows, migrationsDirectory, packagesPattern, parseUses, parseWorkflow, proveExhaustive, rankSkipRules, registry, removeCase, runChecks, settingsMergeMethods, settingsRequiredChecks, settingsReviewThreadResolution, singleStorybook, transitiveNeeds, typeErrors, uphillRules, workflowActionPinning, workflowCredentials, workflowJobOrdering, workflowMergeGroup, workflowReleaseJob, workflowRepositoryDispatch, workflowRunnerResolution, workflowSkippableJobs, workflowUpdateBotCooldown, workflowVersionSingleSource };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "workspace-conformance",
3
3
  "description": "Conformance checks for a pnpm workspace that ESLint cannot express: type-graph, import-graph and cross-file checks driven by the shared layout section of @exadev/config",
4
- "version": "1.4.0",
4
+ "version": "1.6.0",
5
5
  "bin": {
6
6
  "workspace-conformance": "./dist/cli.js"
7
7
  },