@fgv/repo-template 5.1.0-10

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 (81) hide show
  1. package/.rush/temp/9cc3c12fcd90dc622d9e260e68873bfe822f80a1.tar.log +61 -0
  2. package/.rush/temp/chunked-rush-logs/repo-template.build.chunks.jsonl +6 -0
  3. package/.rush/temp/operation/build/all.log +6 -0
  4. package/.rush/temp/operation/build/log-chunks.jsonl +6 -0
  5. package/.rush/temp/operation/build/state.json +3 -0
  6. package/.rush/temp/shrinkwrap-deps.json +576 -0
  7. package/README.md +216 -0
  8. package/bin/repo-template.js +18 -0
  9. package/config/rig.json +4 -0
  10. package/lib/cli.d.ts +14 -0
  11. package/lib/cli.d.ts.map +1 -0
  12. package/lib/cli.js +167 -0
  13. package/lib/cli.js.map +1 -0
  14. package/lib/commands/create.d.ts +17 -0
  15. package/lib/commands/create.d.ts.map +1 -0
  16. package/lib/commands/create.js +212 -0
  17. package/lib/commands/create.js.map +1 -0
  18. package/lib/commands/init-library.d.ts +38 -0
  19. package/lib/commands/init-library.d.ts.map +1 -0
  20. package/lib/commands/init-library.js +269 -0
  21. package/lib/commands/init-library.js.map +1 -0
  22. package/lib/commands/link.d.ts +20 -0
  23. package/lib/commands/link.d.ts.map +1 -0
  24. package/lib/commands/link.js +273 -0
  25. package/lib/commands/link.js.map +1 -0
  26. package/lib/commands/patch.d.ts +15 -0
  27. package/lib/commands/patch.d.ts.map +1 -0
  28. package/lib/commands/patch.js +104 -0
  29. package/lib/commands/patch.js.map +1 -0
  30. package/lib/commands/sync.d.ts +11 -0
  31. package/lib/commands/sync.d.ts.map +1 -0
  32. package/lib/commands/sync.js +156 -0
  33. package/lib/commands/sync.js.map +1 -0
  34. package/lib/index.d.ts +15 -0
  35. package/lib/index.d.ts.map +1 -0
  36. package/lib/index.js +33 -0
  37. package/lib/index.js.map +1 -0
  38. package/lib/packlets/fs/index.d.ts +40 -0
  39. package/lib/packlets/fs/index.d.ts.map +1 -0
  40. package/lib/packlets/fs/index.js +142 -0
  41. package/lib/packlets/fs/index.js.map +1 -0
  42. package/lib/packlets/jsonc/index.d.ts +27 -0
  43. package/lib/packlets/jsonc/index.d.ts.map +1 -0
  44. package/lib/packlets/jsonc/index.js +124 -0
  45. package/lib/packlets/jsonc/index.js.map +1 -0
  46. package/lib/packlets/manifest/index.d.ts +15 -0
  47. package/lib/packlets/manifest/index.d.ts.map +1 -0
  48. package/lib/packlets/manifest/index.js +61 -0
  49. package/lib/packlets/manifest/index.js.map +1 -0
  50. package/lib/packlets/manifest/types.d.ts +33 -0
  51. package/lib/packlets/manifest/types.d.ts.map +1 -0
  52. package/lib/packlets/manifest/types.js +6 -0
  53. package/lib/packlets/manifest/types.js.map +1 -0
  54. package/lib/packlets/template/index.d.ts +22 -0
  55. package/lib/packlets/template/index.d.ts.map +1 -0
  56. package/lib/packlets/template/index.js +75 -0
  57. package/lib/packlets/template/index.js.map +1 -0
  58. package/package.json +32 -0
  59. package/rush-logs/repo-template.build.cache.log +5 -0
  60. package/rush-logs/repo-template.build.log +6 -0
  61. package/src/cli.ts +197 -0
  62. package/src/commands/create.ts +216 -0
  63. package/src/commands/init-library.ts +313 -0
  64. package/src/commands/link.ts +299 -0
  65. package/src/commands/patch.ts +84 -0
  66. package/src/commands/sync.ts +137 -0
  67. package/src/index.ts +22 -0
  68. package/src/packlets/fs/index.ts +114 -0
  69. package/src/packlets/jsonc/index.ts +134 -0
  70. package/src/packlets/manifest/index.ts +29 -0
  71. package/src/packlets/manifest/types.ts +36 -0
  72. package/src/packlets/template/index.ts +48 -0
  73. package/sync-manifest.json +222 -0
  74. package/temp/build/typescript/ts_l9Fw4VUO.json +1 -0
  75. package/templates/.gitignore.tmpl +85 -0
  76. package/templates/ACTIVE_DEVELOPMENT.md.tmpl +58 -0
  77. package/templates/CLAUDE.md.tmpl +124 -0
  78. package/templates/command-line.json.tmpl +50 -0
  79. package/templates/package.json.tmpl +5 -0
  80. package/templates/version-policies.json.tmpl +8 -0
  81. package/tsconfig.json +7 -0
@@ -0,0 +1,313 @@
1
+ /**
2
+ * init-library command — scaffolds a new library package within an existing Rush monorepo.
3
+ */
4
+
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import { patchFile, IPatchOperation } from '../packlets/jsonc';
8
+
9
+ export type DepChannelType = 'alpha' | 'release' | 'auto';
10
+
11
+ /**
12
+ * Check whether a given Rush repo root is the fgv source repo itself
13
+ * (as opposed to a consumer/sibling repo).
14
+ */
15
+ function isFgvRepo(repoDir: string): boolean {
16
+ return fs.existsSync(path.join(repoDir, 'tools', 'repo-template', 'sync-manifest.json'));
17
+ }
18
+
19
+ /**
20
+ * Read the repo-template package's own version from its package.json.
21
+ */
22
+ function getOwnPackageVersion(): string {
23
+ const pkgPath = path.resolve(__dirname, '..', '..', 'package.json');
24
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
25
+ return pkg.version as string;
26
+ }
27
+
28
+ /**
29
+ * Resolve the @fgv/* dependency version spec for init-library.
30
+ *
31
+ * - In the fgv repo itself: returns "workspace:*"
32
+ * - In a sibling/consumer repo: derives a version range from the repo-template's own
33
+ * package version, choosing alpha or release channel.
34
+ *
35
+ * @param repoDir - Rush monorepo root being targeted
36
+ * @param channel - "alpha" for prerelease, "release" for stable, "auto" to infer from
37
+ * whether the repo-template is itself a prerelease build.
38
+ */
39
+ export function resolveFgvDepVersion(repoDir: string, channel: DepChannelType): string {
40
+ if (isFgvRepo(repoDir)) {
41
+ return 'workspace:*';
42
+ }
43
+
44
+ const version = getOwnPackageVersion(); // e.g. "5.1.0-6" or "5.1.0"
45
+ const dashIdx = version.indexOf('-');
46
+ const baseVersion = dashIdx >= 0 ? version.substring(0, dashIdx) : version;
47
+ const isPrerelease = dashIdx >= 0;
48
+
49
+ const useAlpha = channel === 'alpha' || (channel === 'auto' && isPrerelease);
50
+ return useAlpha ? `~${baseVersion}-0` : `~${baseVersion}`;
51
+ }
52
+
53
+ export type RigType = 'dual' | 'node' | 'browser';
54
+ export type CategoryType = 'libraries' | 'tools' | 'apps' | 'services';
55
+
56
+ export interface IInitLibraryOptions {
57
+ /** Package name (e.g. "ts-my-lib" — will be prefixed with @fgv/) */
58
+ name: string;
59
+ /** Short description */
60
+ description: string;
61
+ /** Heft rig to use */
62
+ rig: RigType;
63
+ /** Category folder */
64
+ category: CategoryType;
65
+ /** Rush monorepo root */
66
+ repoDir: string;
67
+ /** Version policy name (from version-policies.json) */
68
+ versionPolicy: string;
69
+ /** Initial version */
70
+ version: string;
71
+ /** Dependency version for @fgv/* packages ("workspace:*" for fgv, "^5.1.0-0" for consumers) */
72
+ fgvDepVersion: string;
73
+ }
74
+
75
+ interface IRigConfig {
76
+ rigPackageName: string;
77
+ rigProfile?: string;
78
+ rigDevDeps: Record<string, string>;
79
+ tsconfigExtends: string;
80
+ tsconfigTypes: string[];
81
+ tsconfigLib?: string[];
82
+ }
83
+
84
+ const RIG_CONFIGS: Record<RigType, IRigConfig> = {
85
+ dual: {
86
+ rigPackageName: '@fgv/heft-dual-rig',
87
+ rigDevDeps: {
88
+ '@fgv/heft-dual-rig': 'FGV_DEP',
89
+ '@rushstack/heft': '1.2.7',
90
+ '@rushstack/heft-node-rig': '2.11.27'
91
+ },
92
+ tsconfigExtends: './node_modules/@rushstack/heft-node-rig/profiles/default/tsconfig-base.json',
93
+ tsconfigTypes: ['heft-jest', 'node'],
94
+ tsconfigLib: ['es2018']
95
+ },
96
+ node: {
97
+ rigPackageName: '@rushstack/heft-node-rig',
98
+ rigDevDeps: {
99
+ '@rushstack/heft': '1.2.7',
100
+ '@rushstack/heft-node-rig': '2.11.27'
101
+ },
102
+ tsconfigExtends: './node_modules/@rushstack/heft-node-rig/profiles/default/tsconfig-base.json',
103
+ tsconfigTypes: ['heft-jest', 'node']
104
+ },
105
+ browser: {
106
+ rigPackageName: '@rushstack/heft-web-rig',
107
+ rigProfile: 'library',
108
+ rigDevDeps: {
109
+ '@rushstack/heft': '1.2.7',
110
+ '@rushstack/heft-web-rig': '1.4.3'
111
+ },
112
+ tsconfigExtends: './node_modules/@rushstack/heft-web-rig/profiles/library/tsconfig-base.json',
113
+ tsconfigTypes: ['heft-jest', 'node'],
114
+ tsconfigLib: ['es2018', 'DOM']
115
+ }
116
+ };
117
+
118
+ export async function runInitLibrary(options: IInitLibraryOptions): Promise<void> {
119
+ const { name, description, rig, category, repoDir, versionPolicy, version, fgvDepVersion } = options;
120
+
121
+ const packageName = name.startsWith('@fgv/') ? name : `@fgv/${name}`;
122
+ const shortName = packageName.replace('@fgv/', '');
123
+ const projectFolder = `${category}/${shortName}`;
124
+ const projectDir = path.join(repoDir, projectFolder);
125
+
126
+ if (fs.existsSync(projectDir)) {
127
+ throw new Error(`Project directory already exists: ${projectDir}`);
128
+ }
129
+
130
+ const rushJsonPath = path.join(repoDir, 'rush.json');
131
+ if (!fs.existsSync(rushJsonPath)) {
132
+ throw new Error(`Not a Rush repo (no rush.json): ${repoDir}`);
133
+ }
134
+
135
+ const rigConfig = RIG_CONFIGS[rig];
136
+
137
+ console.log(`Initializing library: ${packageName}`);
138
+ console.log(` Directory: ${projectFolder}`);
139
+ console.log(` Rig: ${rig} (${rigConfig.rigPackageName})`);
140
+ console.log(` Version: ${versionPolicy}@${version}`);
141
+ console.log('');
142
+
143
+ // ── Create directory structure ──
144
+ fs.mkdirSync(path.join(projectDir, 'src', 'test', 'unit'), { recursive: true });
145
+ fs.mkdirSync(path.join(projectDir, 'config'), { recursive: true });
146
+
147
+ // ── package.json ──
148
+ console.log('==> Creating package.json...');
149
+
150
+ const devDependencies: Record<string, string> = {};
151
+ // Add rig dependencies — @fgv/* packages use fgvDepVersion, others use their pinned version
152
+ for (const [dep, ver] of Object.entries(rigConfig.rigDevDeps)) {
153
+ devDependencies[dep] = dep.startsWith('@fgv/') ? fgvDepVersion : ver;
154
+ }
155
+ // Standard dev dependencies
156
+ devDependencies['@fgv/ts-utils-jest'] = fgvDepVersion;
157
+ devDependencies['@types/heft-jest'] = '1.0.6';
158
+ devDependencies['@types/jest'] = '^29.5.14';
159
+ devDependencies['@types/node'] = '^20.14.9';
160
+ devDependencies['typescript'] = '5.9.3';
161
+ devDependencies['@rushstack/eslint-config'] = '4.6.4';
162
+ devDependencies['eslint'] = '^9.39.2';
163
+ // Typedoc dependencies
164
+ devDependencies['typedoc'] = '~0.28.16';
165
+ devDependencies['@fgv/typedoc-compact-theme'] = fgvDepVersion;
166
+
167
+ const packageJson: Record<string, unknown> = {
168
+ name: packageName,
169
+ version,
170
+ description,
171
+ main: 'lib/index.js',
172
+ types: 'lib/index.d.ts',
173
+ scripts: {
174
+ build: 'heft build --clean',
175
+ clean: 'heft clean',
176
+ test: 'heft test --clean',
177
+ coverage: 'jest --coverage',
178
+ 'build-docs': 'typedoc --options ./config/typedoc.json',
179
+ lint: 'eslint src --ext .ts',
180
+ fixlint: 'eslint src --ext .ts --fix'
181
+ },
182
+ author: '',
183
+ license: 'MIT',
184
+ dependencies: {
185
+ '@fgv/ts-utils': fgvDepVersion,
186
+ '@fgv/ts-json-base': fgvDepVersion
187
+ },
188
+ devDependencies,
189
+ repository: {
190
+ type: 'git',
191
+ url: ''
192
+ }
193
+ };
194
+
195
+ // Add dual-emit exports for dual rig
196
+ if (rig === 'dual') {
197
+ packageJson['module'] = 'dist/index.js';
198
+ packageJson['exports'] = {
199
+ '.': {
200
+ types: './lib/index.d.ts',
201
+ import: './dist/index.js',
202
+ require: './lib/index.js',
203
+ default: './lib/index.js'
204
+ }
205
+ };
206
+ }
207
+
208
+ fs.writeFileSync(path.join(projectDir, 'package.json'), JSON.stringify(packageJson, null, 2) + '\n');
209
+
210
+ // ── tsconfig.json ──
211
+ console.log(' Creating tsconfig.json...');
212
+
213
+ const tsconfig: Record<string, unknown> = {
214
+ extends: rigConfig.tsconfigExtends,
215
+ compilerOptions: {
216
+ types: rigConfig.tsconfigTypes,
217
+ ...(rigConfig.tsconfigLib ? { lib: rigConfig.tsconfigLib } : {})
218
+ }
219
+ };
220
+
221
+ fs.writeFileSync(path.join(projectDir, 'tsconfig.json'), JSON.stringify(tsconfig, null, 2) + '\n');
222
+
223
+ // ── config/rig.json ──
224
+ console.log(' Creating config/rig.json...');
225
+
226
+ const rigJson: Record<string, unknown> = {
227
+ $schema: 'https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json',
228
+ rigPackageName: rigConfig.rigPackageName
229
+ };
230
+ if (rigConfig.rigProfile) {
231
+ rigJson['rigProfile'] = rigConfig.rigProfile;
232
+ }
233
+
234
+ fs.writeFileSync(path.join(projectDir, 'config', 'rig.json'), JSON.stringify(rigJson, null, 2) + '\n');
235
+
236
+ // ── config/jest.config.json ──
237
+ console.log(' Creating config/jest.config.json...');
238
+
239
+ const jestConfig = {
240
+ extends: '@rushstack/heft-node-rig/profiles/default/config/jest.config.json',
241
+ coverageThreshold: {
242
+ global: {
243
+ branches: 100,
244
+ functions: 100,
245
+ lines: 100,
246
+ statements: 100
247
+ }
248
+ },
249
+ collectCoverage: true,
250
+ coverageReporters: ['text', 'lcov', 'html']
251
+ };
252
+
253
+ fs.writeFileSync(
254
+ path.join(projectDir, 'config', 'jest.config.json'),
255
+ JSON.stringify(jestConfig, null, 2) + '\n'
256
+ );
257
+
258
+ // ── config/typedoc.json ──
259
+ console.log(' Creating config/typedoc.json...');
260
+
261
+ const typedocConfig = {
262
+ $schema: 'https://typedoc.org/schema.json',
263
+ extends: ['@fgv/heft-dual-rig/profiles/default/config/typedoc.compact-base.json'],
264
+ plugin: ['../../../plugins/typedoc-compact-theme/lib/index.js'],
265
+ entryPoints: ['../src/index.ts'],
266
+ out: '../docs'
267
+ };
268
+
269
+ fs.writeFileSync(
270
+ path.join(projectDir, 'config', 'typedoc.json'),
271
+ JSON.stringify(typedocConfig, null, 2) + '\n'
272
+ );
273
+
274
+ // ── src/index.ts ──
275
+ console.log(' Creating src/index.ts...');
276
+
277
+ fs.writeFileSync(
278
+ path.join(projectDir, 'src', 'index.ts'),
279
+ `/**\n * @packageDocumentation\n * ${description}\n */\n`
280
+ );
281
+
282
+ // ── Register in rush.json ──
283
+ console.log('');
284
+ console.log('==> Registering in rush.json...');
285
+
286
+ const rushJsonOps: IPatchOperation[] = [
287
+ {
288
+ type: 'add-to-array',
289
+ path: 'projects',
290
+ value: JSON.stringify({
291
+ packageName,
292
+ projectFolder,
293
+ shouldPublish: true,
294
+ versionPolicyName: versionPolicy,
295
+ tags: [category]
296
+ })
297
+ }
298
+ ];
299
+
300
+ patchFile(rushJsonPath, rushJsonOps);
301
+ console.log(` Added ${packageName} at ${projectFolder}`);
302
+
303
+ // ── Done ──
304
+ console.log('');
305
+ console.log(`=== Library ${packageName} initialized at ${projectFolder} ===`);
306
+ console.log('');
307
+ console.log('Next steps:');
308
+ console.log(` 1. cd ${projectDir}`);
309
+ console.log(' 2. rush update');
310
+ console.log(' 3. rushx build');
311
+ console.log(' 4. Start adding code to src/');
312
+ console.log('');
313
+ }
@@ -0,0 +1,299 @@
1
+ /**
2
+ * Link/unlink/update-fgv-versions commands — automate cross-repo development
3
+ * between fgv and consumer Rush monorepos.
4
+ */
5
+
6
+ import * as fs from 'fs';
7
+ import * as path from 'path';
8
+ import { execSync } from 'child_process';
9
+ import { parse as parseJsonc } from 'jsonc-parser';
10
+ import { IPatchOperation, patchFile } from '../packlets/jsonc';
11
+
12
+ // ── Interfaces ──
13
+
14
+ export interface ILinkOptions {
15
+ fgvDir: string;
16
+ repoDir: string;
17
+ }
18
+
19
+ export interface IUnlinkOptions {
20
+ repoDir: string;
21
+ version?: string;
22
+ }
23
+
24
+ export interface IUpdateFgvVersionsOptions {
25
+ repoDir: string;
26
+ version?: string;
27
+ }
28
+
29
+ interface IRushProject {
30
+ packageName: string;
31
+ projectFolder: string;
32
+ }
33
+
34
+ // ── Discovery helpers ──
35
+
36
+ /**
37
+ * Read a rush.json and return its project entries.
38
+ */
39
+ function readRushProjects(rushJsonPath: string): IRushProject[] {
40
+ const content = fs.readFileSync(rushJsonPath, 'utf-8');
41
+ const rush = parseJsonc(content) as { projects: IRushProject[] };
42
+ return rush.projects;
43
+ }
44
+
45
+ /**
46
+ * Scan a consumer Rush repo and collect all @fgv/* dependencies (excluding workspace:* entries).
47
+ * Returns a map of packageName -> list of project folders that depend on it.
48
+ */
49
+ function discoverFgvDeps(repoDir: string): Map<string, string[]> {
50
+ const rushJsonPath = path.join(repoDir, 'rush.json');
51
+ if (!fs.existsSync(rushJsonPath)) {
52
+ throw new Error(`rush.json not found at ${rushJsonPath}`);
53
+ }
54
+
55
+ const projects = readRushProjects(rushJsonPath);
56
+ const deps = new Map<string, string[]>();
57
+
58
+ for (const project of projects) {
59
+ const pkgJsonPath = path.join(repoDir, project.projectFolder, 'package.json');
60
+ if (!fs.existsSync(pkgJsonPath)) {
61
+ continue;
62
+ }
63
+
64
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
65
+ const allDeps: Record<string, string> = {
66
+ ...pkgJson.dependencies,
67
+ ...pkgJson.devDependencies
68
+ };
69
+
70
+ for (const [name, spec] of Object.entries(allDeps)) {
71
+ if (name.startsWith('@fgv/') && spec !== 'workspace:*') {
72
+ const existing = deps.get(name) ?? [];
73
+ existing.push(project.projectFolder);
74
+ deps.set(name, existing);
75
+ }
76
+ }
77
+ }
78
+
79
+ return deps;
80
+ }
81
+
82
+ /**
83
+ * Read the fgv worktree's rush.json and build a map of packageName -> projectFolder.
84
+ */
85
+ function buildFgvPackageMap(fgvDir: string): Map<string, string> {
86
+ const rushJsonPath = path.join(fgvDir, 'rush.json');
87
+ if (!fs.existsSync(rushJsonPath)) {
88
+ throw new Error(`fgv rush.json not found at ${rushJsonPath}`);
89
+ }
90
+
91
+ const projects = readRushProjects(rushJsonPath);
92
+ const map = new Map<string, string>();
93
+ for (const project of projects) {
94
+ map.set(project.packageName, project.projectFolder);
95
+ }
96
+ return map;
97
+ }
98
+
99
+ // ── Version helpers ──
100
+
101
+ /**
102
+ * Query npm for the latest prerelease version of @fgv/ts-utils and construct a ~X.Y.Z spec.
103
+ */
104
+ function resolveLatestFgvVersion(): string {
105
+ const output = execSync('npm view @fgv/ts-utils dist-tags --json', { encoding: 'utf-8' });
106
+ const tags = JSON.parse(output) as Record<string, string>;
107
+ const version = tags.alpha ?? tags.latest;
108
+ if (!version) {
109
+ throw new Error('Could not determine latest @fgv version from npm dist-tags');
110
+ }
111
+ return `~${version}`;
112
+ }
113
+
114
+ /**
115
+ * Update all @fgv/* dependency versions (excluding workspace:*) in a consumer repo's package.json files.
116
+ * Uses jsonc-parser modify() to preserve formatting.
117
+ */
118
+ function updateVersionsInPackageJsons(repoDir: string, versionSpec: string): string[] {
119
+ const rushJsonPath = path.join(repoDir, 'rush.json');
120
+ const projects = readRushProjects(rushJsonPath);
121
+ const changed: string[] = [];
122
+
123
+ for (const project of projects) {
124
+ const pkgJsonPath = path.join(repoDir, project.projectFolder, 'package.json');
125
+ if (!fs.existsSync(pkgJsonPath)) {
126
+ continue;
127
+ }
128
+
129
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
130
+ const operations: IPatchOperation[] = [];
131
+
132
+ for (const depField of ['dependencies', 'devDependencies'] as const) {
133
+ const deps: Record<string, string> | undefined = pkgJson[depField];
134
+ if (!deps) continue;
135
+
136
+ for (const [name, spec] of Object.entries(deps)) {
137
+ if (name.startsWith('@fgv/') && spec !== 'workspace:*') {
138
+ operations.push({
139
+ type: 'set-json',
140
+ path: `${depField}.${name}`,
141
+ value: JSON.stringify(versionSpec)
142
+ });
143
+ }
144
+ }
145
+ }
146
+
147
+ if (operations.length > 0) {
148
+ patchFile(pkgJsonPath, operations);
149
+ changed.push(pkgJsonPath);
150
+ }
151
+ }
152
+
153
+ return changed;
154
+ }
155
+
156
+ // ── Command runners ──
157
+
158
+ export async function runLink(options: ILinkOptions): Promise<void> {
159
+ const { fgvDir, repoDir } = options;
160
+
161
+ console.log(`==> Linking to local fgv worktree`);
162
+ console.log(` fgv dir: ${fgvDir}`);
163
+ console.log(` repo dir: ${repoDir}`);
164
+
165
+ const fgvDeps = discoverFgvDeps(repoDir);
166
+ if (fgvDeps.size === 0) {
167
+ console.log(' No @fgv/* dependencies found in consumer repo.');
168
+ return;
169
+ }
170
+
171
+ const fgvPackageMap = buildFgvPackageMap(fgvDir);
172
+
173
+ // Compute file: overrides relative to common/temp/
174
+ const commonTempDir = path.join(repoDir, 'common', 'temp');
175
+ const overrides: Record<string, string> = {};
176
+ const warnings: string[] = [];
177
+
178
+ for (const packageName of fgvDeps.keys()) {
179
+ const projectFolder = fgvPackageMap.get(packageName);
180
+ if (!projectFolder) {
181
+ warnings.push(` Warning: ${packageName} not found in fgv worktree — skipping`);
182
+ continue;
183
+ }
184
+ const absoluteProjectPath = path.join(fgvDir, projectFolder);
185
+ const relativePath = path.relative(commonTempDir, absoluteProjectPath);
186
+ overrides[packageName] = `file:${relativePath}`;
187
+ }
188
+
189
+ for (const w of warnings) {
190
+ console.warn(w);
191
+ }
192
+
193
+ // Patch pnpm-config.json
194
+ const pnpmConfigPath = path.join(repoDir, 'common', 'config', 'rush', 'pnpm-config.json');
195
+ if (!fs.existsSync(pnpmConfigPath)) {
196
+ throw new Error(`pnpm-config.json not found at ${pnpmConfigPath}`);
197
+ }
198
+
199
+ const patchOps: IPatchOperation[] = [
200
+ {
201
+ type: 'set-json',
202
+ path: 'globalOverrides',
203
+ value: JSON.stringify(overrides)
204
+ },
205
+ {
206
+ type: 'set',
207
+ path: 'strictPeerDependencies',
208
+ value: false
209
+ },
210
+ {
211
+ type: 'set-json',
212
+ path: 'globalPeerDependencyRules',
213
+ value: JSON.stringify({ ignoreMissing: ['mustache'], allowAny: ['@fgv/*'] })
214
+ }
215
+ ];
216
+
217
+ patchFile(pnpmConfigPath, patchOps);
218
+
219
+ console.log(`\n Patched: ${pnpmConfigPath}`);
220
+ console.log(` Overrides (${Object.keys(overrides).length} packages):`);
221
+ for (const [name, filePath] of Object.entries(overrides)) {
222
+ console.log(` ${name} -> ${filePath}`);
223
+ }
224
+ console.log(`\n Next: run "rush update --purge" in the consumer repo.`);
225
+ }
226
+
227
+ export async function runUnlink(options: IUnlinkOptions): Promise<void> {
228
+ const { repoDir, version } = options;
229
+
230
+ console.log(`==> Unlinking from local fgv worktree`);
231
+ console.log(` repo dir: ${repoDir}`);
232
+
233
+ // Restore pnpm-config.json to defaults
234
+ const pnpmConfigPath = path.join(repoDir, 'common', 'config', 'rush', 'pnpm-config.json');
235
+ if (!fs.existsSync(pnpmConfigPath)) {
236
+ throw new Error(`pnpm-config.json not found at ${pnpmConfigPath}`);
237
+ }
238
+
239
+ const patchOps: IPatchOperation[] = [
240
+ {
241
+ type: 'set-json',
242
+ path: 'globalOverrides',
243
+ value: JSON.stringify({})
244
+ },
245
+ {
246
+ type: 'set',
247
+ path: 'strictPeerDependencies',
248
+ value: true
249
+ },
250
+ {
251
+ type: 'set-json',
252
+ path: 'globalPeerDependencyRules',
253
+ value: JSON.stringify({})
254
+ }
255
+ ];
256
+
257
+ patchFile(pnpmConfigPath, patchOps);
258
+ console.log(` Patched: ${pnpmConfigPath} (restored defaults)`);
259
+
260
+ // Optionally bump versions
261
+ if (version) {
262
+ console.log(`\n Bumping @fgv/* deps to ${version}...`);
263
+ const changed = updateVersionsInPackageJsons(repoDir, version);
264
+ console.log(` Updated ${changed.length} package.json file(s):`);
265
+ for (const f of changed) {
266
+ console.log(` ${f}`);
267
+ }
268
+ }
269
+
270
+ console.log(`\n Next: run "rush update --purge" in the consumer repo.`);
271
+ }
272
+
273
+ export async function runUpdateFgvVersions(options: IUpdateFgvVersionsOptions): Promise<void> {
274
+ const { repoDir } = options;
275
+
276
+ console.log(`==> Updating @fgv/* version specs`);
277
+ console.log(` repo dir: ${repoDir}`);
278
+
279
+ // Resolve version
280
+ let versionSpec: string;
281
+ if (options.version) {
282
+ versionSpec = options.version;
283
+ console.log(` Using explicit version: ${versionSpec}`);
284
+ } else {
285
+ console.log(' Querying npm for latest version...');
286
+ versionSpec = resolveLatestFgvVersion();
287
+ console.log(` Resolved version: ${versionSpec}`);
288
+ }
289
+
290
+ const changed = updateVersionsInPackageJsons(repoDir, versionSpec);
291
+ console.log(`\n Updated ${changed.length} package.json file(s):`);
292
+ for (const f of changed) {
293
+ console.log(` ${f}`);
294
+ }
295
+
296
+ if (changed.length > 0) {
297
+ console.log(`\n Next: run "rush update --purge" in the consumer repo.`);
298
+ }
299
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Patch command — apply targeted edits to JSONC config files.
3
+ */
4
+
5
+ import * as fs from 'fs';
6
+ import { IPatchOperation, PatchOperationType, applyOperations, parseValue } from '../packlets/jsonc';
7
+
8
+ export interface IPatchOptions {
9
+ file: string;
10
+ operations: IPatchOperation[];
11
+ }
12
+
13
+ /**
14
+ * Parse CLI arguments for the patch command into operations.
15
+ * Expects pairs like: --set 'path=value', --uncomment 'path', etc.
16
+ */
17
+ export function parsePatchArgs(args: string[]): IPatchOperation[] {
18
+ const operations: IPatchOperation[] = [];
19
+ const validOps: PatchOperationType[] = ['set', 'set-json', 'uncomment', 'add-to-array', 'remove'];
20
+
21
+ let i = 0;
22
+ while (i < args.length) {
23
+ const arg = args[i];
24
+ if (!arg.startsWith('--')) {
25
+ throw new Error(`Unexpected argument: ${arg}`);
26
+ }
27
+
28
+ const opType = arg.slice(2) as PatchOperationType;
29
+ if (!validOps.includes(opType)) {
30
+ throw new Error(`Unknown operation: ${arg}`);
31
+ }
32
+
33
+ i++;
34
+ if (i >= args.length) {
35
+ throw new Error(`Missing value for ${arg}`);
36
+ }
37
+
38
+ const operand = args[i];
39
+ i++;
40
+
41
+ if (opType === 'uncomment' || opType === 'remove') {
42
+ operations.push({ type: opType, path: operand });
43
+ } else {
44
+ const eqIndex = operand.indexOf('=');
45
+ if (eqIndex === -1) {
46
+ throw new Error(`Expected path=value for ${arg}, got: ${operand}`);
47
+ }
48
+ const opPath = operand.slice(0, eqIndex);
49
+ const rawValue = operand.slice(eqIndex + 1);
50
+
51
+ if (opType === 'set') {
52
+ operations.push({ type: opType, path: opPath, value: parseValue(rawValue) });
53
+ } else {
54
+ // set-json and add-to-array pass raw JSON string
55
+ operations.push({ type: opType, path: opPath, value: rawValue });
56
+ }
57
+ }
58
+ }
59
+
60
+ return operations;
61
+ }
62
+
63
+ export async function runPatch(options: IPatchOptions): Promise<void> {
64
+ const { file, operations } = options;
65
+
66
+ if (!fs.existsSync(file)) {
67
+ throw new Error(`File not found: ${file}`);
68
+ }
69
+
70
+ console.log(`Patching: ${file}`);
71
+ let source = fs.readFileSync(file, 'utf-8');
72
+
73
+ for (const op of operations) {
74
+ const desc =
75
+ op.type === 'uncomment' || op.type === 'remove'
76
+ ? ` ${op.type}: ${op.path}`
77
+ : ` ${op.type}: ${op.path} = ${op.value}`;
78
+ console.log(desc);
79
+ }
80
+
81
+ source = applyOperations(source, operations);
82
+ fs.writeFileSync(file, source);
83
+ console.log(` Done: ${operations.length} operation(s) applied`);
84
+ }