Skip to content

Commit a500f8f

Browse files
Copiloticlanton
andauthored
feat(rush-lib): pilot heft-zod-schema-plugin with experiments.zod.ts
Agent-Logs-Url: https://github.com/microsoft/rushstack/sessions/f6417dd3-99b5-4eb5-9343-ecf5de6c37c4 Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com>
1 parent b7da035 commit a500f8f

9 files changed

Lines changed: 205 additions & 8 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@microsoft/rush-lib",
5+
"comment": "Pilot for @rushstack/heft-zod-schema-plugin: introduce experiments.zod.ts as a parallel zod-based source of truth for experiments.json. The hand-authored IExperimentsJson interface and experiments.schema.json remain unchanged; a compile-time assertion now guarantees the zod schema stays in sync with the interface.",
6+
"type": "none"
7+
}
8+
],
9+
"packageName": "@microsoft/rush-lib"
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@rushstack/heft-zod-schema-plugin",
5+
"comment": "Initial release. A Heft task plugin that converts zod validators into *.schema.json build outputs using zod 4's built-in z.toJSONSchema(). Includes a withSchemaMeta() helper for attaching $schema/title/description/x-tsdoc-release-tag metadata to a zod schema.",
6+
"type": "minor"
7+
}
8+
],
9+
"packageName": "@rushstack/heft-zod-schema-plugin"
10+
}

common/config/rush/browser-approved-packages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@
3838
"name": "@reduxjs/toolkit",
3939
"allowedCategories": [ "libraries", "vscode-extensions" ]
4040
},
41+
{
42+
"name": "@rushstack/heft-zod-schema-plugin",
43+
"allowedCategories": [ "libraries" ]
44+
},
4145
{
4246
"name": "@rushstack/problem-matcher",
4347
"allowedCategories": [ "libraries" ]

common/config/subspaces/build-tests-subspace/pnpm-lock.yaml

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush.
22
{
3-
"pnpmShrinkwrapHash": "1266218fdf9ed4d67e625f96e8c1cc4bae29dc68",
3+
"pnpmShrinkwrapHash": "4497f5b169c39c6d45b2f351e4484d4879102c71",
44
"preferredVersionsHash": "550b4cee0bef4e97db6c6aad726df5149d20e7d9",
5-
"packageJsonInjectedDependenciesHash": "9c068bf4931bd84aa82934f391073bf027e52b69"
5+
"packageJsonInjectedDependenciesHash": "e7087a8987565f709ad2f841d39cd7078a57d8ad"
66
}

common/config/subspaces/default/pnpm-lock.yaml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

libraries/rush-lib/config/heft.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,23 @@
9797
]
9898
}
9999
}
100+
},
101+
102+
// PILOT (heft-zod-schema-plugin): generate the experiments.json
103+
// JSON Schema from the colocated experiments.zod.ts source file.
104+
// Writes to temp/zod-schemas to avoid colliding with the legacy
105+
// hand-authored schema in src/schemas/. The generated file is for
106+
// review during the pilot; the runtime still uses the legacy schema.
107+
"zod-schemas": {
108+
"taskDependencies": ["typescript"],
109+
"taskPlugin": {
110+
"pluginPackage": "@rushstack/heft-zod-schema-plugin",
111+
"pluginName": "zod-schema-plugin",
112+
"options": {
113+
"inputGlobs": ["lib-intermediate-commonjs/schemas/*.zod.js"],
114+
"outputFolder": "temp/zod-schemas"
115+
}
116+
}
100117
}
101118
}
102119
}

libraries/rush-lib/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
"devDependencies": {
8585
"@pnpm/lockfile.types-900": "npm:@pnpm/lockfile.types@~900.0.0",
8686
"@rushstack/heft-webpack5-plugin": "workspace:*",
87+
"@rushstack/heft-zod-schema-plugin": "workspace:*",
8788
"@rushstack/heft": "workspace:*",
8889
"@rushstack/operation-graph": "workspace:*",
8990
"@rushstack/webpack-deep-imports-plugin": "workspace:*",
@@ -98,7 +99,8 @@
9899
"@types/webpack-env": "1.18.8",
99100
"eslint": "~9.37.0",
100101
"local-node-rig": "workspace:*",
101-
"webpack": "~5.105.2"
102+
"webpack": "~5.105.2",
103+
"zod": "~4.3.6"
102104
},
103105
"publishOnlyDependencies": {
104106
"@rushstack/rush-amazon-s3-build-cache-plugin": "workspace:*",
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2+
// See LICENSE in the project root for license information.
3+
4+
// PILOT: zod-based source-of-truth for experiments.json.
5+
//
6+
// This module is the long-term replacement for the hand-authored
7+
// `experiments.schema.json` that lives next to it. The companion legacy schema
8+
// is intentionally kept in place during the pilot so reviewers can diff the
9+
// generated output against it. See the parent PR description for context.
10+
//
11+
// To preserve the existing rush-lib public API surface during the pilot, the
12+
// `IExperimentsJson` interface in `ExperimentsConfiguration.ts` is left
13+
// unchanged. The compile-time assertion at the bottom of this file guarantees
14+
// that the zod schema stays structurally equivalent to that interface; if they
15+
// ever drift, the build fails.
16+
//
17+
// At build time, `@rushstack/heft-zod-schema-plugin` reads the compiled form
18+
// of this module and emits a generated `experiments.schema.json` for review.
19+
20+
import { z } from 'zod';
21+
22+
import { withSchemaMeta } from '@rushstack/heft-zod-schema-plugin/lib/SchemaMetaHelpers';
23+
24+
import type { IExperimentsJson } from '../api/ExperimentsConfiguration';
25+
26+
const booleanFlag = (description: string): z.ZodOptional<z.ZodBoolean> =>
27+
z.boolean().describe(description).optional();
28+
29+
/**
30+
* The zod schema describing the structure of `experiments.json`.
31+
*
32+
* @internal
33+
*/
34+
// eslint-disable-next-line @typescript-eslint/typedef
35+
export const experimentsSchema = withSchemaMeta(
36+
z
37+
.object({
38+
$schema: z
39+
.string()
40+
.describe(
41+
'Part of the JSON Schema standard, this optional keyword declares the URL of the schema that the file conforms to. ' +
42+
'Editors may download the schema and use it to perform syntax highlighting.'
43+
)
44+
.optional(),
45+
46+
usePnpmFrozenLockfileForRushInstall: booleanFlag(
47+
"By default, 'rush install' passes --no-prefer-frozen-lockfile to 'pnpm install'. " +
48+
"Set this option to true to pass '--frozen-lockfile' instead."
49+
),
50+
usePnpmPreferFrozenLockfileForRushUpdate: booleanFlag(
51+
"By default, 'rush update' passes --no-prefer-frozen-lockfile to 'pnpm install'. " +
52+
"Set this option to true to pass '--prefer-frozen-lockfile' instead."
53+
),
54+
usePnpmLockfileOnlyThenFrozenLockfileForRushUpdate: booleanFlag(
55+
"By default, 'rush update' runs as a single operation. Set this option to true to instead update the lockfile with `--lockfile-only`, then perform a `--frozen-lockfile` install. " +
56+
'Necessary when using the `afterAllResolved` hook in .pnpmfile.cjs.'
57+
),
58+
omitImportersFromPreventManualShrinkwrapChanges: booleanFlag(
59+
"If using the 'preventManualShrinkwrapChanges' option, only prevent manual changes to the total set of external dependencies referenced by the repository, not which projects reference which dependencies. " +
60+
'This offers a balance between lockfile integrity and merge conflicts.'
61+
),
62+
noChmodFieldInTarHeaderNormalization: booleanFlag(
63+
'If true, the chmod field in temporary project tar headers will not be normalized. This normalization can help ensure consistent tarball integrity across platforms.'
64+
),
65+
buildCacheWithAllowWarningsInSuccessfulBuild: booleanFlag(
66+
'If true, build caching will respect the allowWarningsInSuccessfulBuild flag and cache builds with warnings. This will not replay warnings from the cached build.'
67+
),
68+
buildSkipWithAllowWarningsInSuccessfulBuild: booleanFlag(
69+
'If true, build skipping will respect the allowWarningsInSuccessfulBuild flag and skip builds with warnings. This will not replay warnings from the skipped build.'
70+
),
71+
phasedCommands: booleanFlag(
72+
'THIS EXPERIMENT HAS BEEN GRADUATED TO A STANDARD FEATURE. THIS PROPERTY SHOULD BE REMOVED.'
73+
),
74+
cleanInstallAfterNpmrcChanges: booleanFlag(
75+
'If true, perform a clean install after when running `rush install` or `rush update` if the `.npmrc` file has changed since the last install.'
76+
),
77+
printEventHooksOutputToConsole: booleanFlag(
78+
'If true, print the outputs of shell commands defined in event hooks to the console.'
79+
),
80+
forbidPhantomResolvableNodeModulesFolders: booleanFlag(
81+
'If true, Rush will not allow node_modules in the repo folder or in parent folders.'
82+
),
83+
usePnpmSyncForInjectedDependencies: booleanFlag(
84+
"(UNDER DEVELOPMENT) For certain installation problems involving peer dependencies, PNPM cannot correctly satisfy versioning requirements without installing duplicate copies of a package inside the node_modules folder. This poses a problem for 'workspace:*' dependencies, as they are normally installed by making a symlink to the local project source folder. PNPM's 'injected dependencies' feature provides a model for copying the local project folder into node_modules, however copying must occur AFTER the dependency project is built and BEFORE the consuming project starts to build. The 'pnpm-sync' tool manages this operation; see its documentation for details. Enable this experiment if you want 'rush' and 'rushx' commands to resync injected dependencies by invoking 'pnpm-sync' during the build."
85+
),
86+
generateProjectImpactGraphDuringRushUpdate: booleanFlag(
87+
'If set to true, Rush will generate a `project-impact-graph.yaml` file in the repository root during `rush update`.'
88+
),
89+
useIPCScriptsInWatchMode: booleanFlag(
90+
'If true, when running in watch mode, Rush will check for phase scripts named `_phase:<name>:ipc` and run them instead of `_phase:<name>` if they exist. The created child process will be provided with an IPC channel and expected to persist across invocations.'
91+
),
92+
allowCobuildWithoutCache: booleanFlag(
93+
'When using cobuilds, this experiment allows uncacheable operations to benefit from cobuild orchestration without using the build cache.'
94+
),
95+
rushAlerts: booleanFlag(
96+
"(UNDER DEVELOPMENT) The Rush alerts feature provides a way to send announcements to engineers working in the monorepo, by printing directly in the user's shell window when they invoke Rush commands. This ensures that important notices will be seen by anyone doing active development, since people often ignore normal discussion group messages or don't know to subscribe."
97+
),
98+
enableSubpathScan: booleanFlag(
99+
'By default, rush perform a full scan of the entire repository. For example, Rush runs `git status` to check for local file changes. When this toggle is enabled, Rush will only scan specific paths, significantly speeding up Git operations.'
100+
),
101+
exemptDecoupledDependenciesBetweenSubspaces: booleanFlag(
102+
'Rush has a policy that normally requires Rush projects to specify `workspace:*` in package.json when depending on other projects in the workspace, unless they are explicitly declared as `decoupledLocalDependencies in rush.json. Enabling this experiment will remove that requirement for dependencies belonging to a different subspace. This is useful for large product groups who work in separate subspaces and generally prefer to consume each other\'s packages via the NPM registry.'
103+
),
104+
omitAppleDoubleFilesFromBuildCache: booleanFlag(
105+
'If true, when running on macOS, Rush will omit AppleDouble files (._*) from build cache archives when a companion file exists in the same directory. AppleDouble files are automatically created by macOS to store extended attributes on filesystems that don\'t support them, and should generally not be included in the shared build cache.'
106+
),
107+
strictChangefileValidation: booleanFlag(
108+
'If true, `rush change --verify` will report errors if change files reference projects that do not exist in the Rush configuration, or if change files target a project that belongs to a lockstepped version policy but is not the policy\'s main project.'
109+
)
110+
})
111+
.strict(),
112+
{
113+
$schema: 'http://json-schema.org/draft-04/schema#',
114+
title: 'Rush experiments.json config file',
115+
description:
116+
'For use with the Rush tool, this file allows repo maintainers to enable and disable experimental Rush features.',
117+
releaseTag: '@beta'
118+
}
119+
);
120+
121+
/**
122+
* Helper that maps over the keys of `T` to coerce TypeScript into rendering the
123+
* fully-expanded shape of an inferred type.
124+
*/
125+
type _Simplify<T> = T extends infer U ? { [K in keyof U]: U[K] } : never;
126+
127+
/**
128+
* Compile-time assertion that the zod schema is structurally equivalent to the
129+
* hand-authored `IExperimentsJson` interface in `ExperimentsConfiguration.ts`.
130+
* If the two ever drift (for example, a new experiment is added in only one
131+
* place), this will fail the build.
132+
*
133+
* @internal
134+
*/
135+
export type _ExperimentsJsonZodMatches = _Simplify<z.infer<typeof experimentsSchema>> extends IExperimentsJson
136+
? IExperimentsJson extends _Simplify<z.infer<typeof experimentsSchema>>
137+
? true
138+
: { error: 'IExperimentsJson is missing properties present on z.infer<typeof experimentsSchema>' }
139+
: { error: 'z.infer<typeof experimentsSchema> is missing properties present on IExperimentsJson' };
140+
141+
const _typeCheck: _ExperimentsJsonZodMatches = true;
142+
// Reference the unused binding so the linter is happy.
143+
void _typeCheck;
144+
145+
// Default export so the heft-zod-schema-plugin emits this as
146+
// `experiments.schema.json` (rather than `experiments.experimentsSchema.schema.json`
147+
// when configured with `exportName: "*"`).
148+
export default experimentsSchema;

0 commit comments

Comments
 (0)