@fgv/repo-template 5.1.0-2

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 (77) hide show
  1. package/.rush/temp/45a8d0dcbb9c2e59fa02645657661dffbd25461f.tar.log +57 -0
  2. package/.rush/temp/92e27a75687fa5062b71fdb897f0928a8aa4e0c9.tar.log +57 -0
  3. package/.rush/temp/chunked-rush-logs/repo-template.build.chunks.jsonl +7 -0
  4. package/.rush/temp/operation/build/all.log +7 -0
  5. package/.rush/temp/operation/build/log-chunks.jsonl +7 -0
  6. package/.rush/temp/operation/build/state.json +3 -0
  7. package/.rush/temp/shrinkwrap-deps.json +576 -0
  8. package/README.md +216 -0
  9. package/bin/repo-template.js +18 -0
  10. package/config/rig.json +4 -0
  11. package/lib/cli.d.ts +14 -0
  12. package/lib/cli.d.ts.map +1 -0
  13. package/lib/cli.js +126 -0
  14. package/lib/cli.js.map +1 -0
  15. package/lib/commands/create.d.ts +17 -0
  16. package/lib/commands/create.d.ts.map +1 -0
  17. package/lib/commands/create.js +212 -0
  18. package/lib/commands/create.js.map +1 -0
  19. package/lib/commands/init-library.d.ts +25 -0
  20. package/lib/commands/init-library.d.ts.map +1 -0
  21. package/lib/commands/init-library.js +217 -0
  22. package/lib/commands/init-library.js.map +1 -0
  23. package/lib/commands/patch.d.ts +15 -0
  24. package/lib/commands/patch.d.ts.map +1 -0
  25. package/lib/commands/patch.js +104 -0
  26. package/lib/commands/patch.js.map +1 -0
  27. package/lib/commands/sync.d.ts +11 -0
  28. package/lib/commands/sync.d.ts.map +1 -0
  29. package/lib/commands/sync.js +156 -0
  30. package/lib/commands/sync.js.map +1 -0
  31. package/lib/index.d.ts +14 -0
  32. package/lib/index.d.ts.map +1 -0
  33. package/lib/index.js +29 -0
  34. package/lib/index.js.map +1 -0
  35. package/lib/packlets/fs/index.d.ts +40 -0
  36. package/lib/packlets/fs/index.d.ts.map +1 -0
  37. package/lib/packlets/fs/index.js +142 -0
  38. package/lib/packlets/fs/index.js.map +1 -0
  39. package/lib/packlets/jsonc/index.d.ts +27 -0
  40. package/lib/packlets/jsonc/index.d.ts.map +1 -0
  41. package/lib/packlets/jsonc/index.js +124 -0
  42. package/lib/packlets/jsonc/index.js.map +1 -0
  43. package/lib/packlets/manifest/index.d.ts +15 -0
  44. package/lib/packlets/manifest/index.d.ts.map +1 -0
  45. package/lib/packlets/manifest/index.js +61 -0
  46. package/lib/packlets/manifest/index.js.map +1 -0
  47. package/lib/packlets/manifest/types.d.ts +33 -0
  48. package/lib/packlets/manifest/types.d.ts.map +1 -0
  49. package/lib/packlets/manifest/types.js +6 -0
  50. package/lib/packlets/manifest/types.js.map +1 -0
  51. package/lib/packlets/template/index.d.ts +22 -0
  52. package/lib/packlets/template/index.d.ts.map +1 -0
  53. package/lib/packlets/template/index.js +75 -0
  54. package/lib/packlets/template/index.js.map +1 -0
  55. package/package.json +32 -0
  56. package/rush-logs/repo-template.build.cache.log +4 -0
  57. package/rush-logs/repo-template.build.log +7 -0
  58. package/src/cli.ts +141 -0
  59. package/src/commands/create.ts +216 -0
  60. package/src/commands/init-library.ts +249 -0
  61. package/src/commands/patch.ts +84 -0
  62. package/src/commands/sync.ts +137 -0
  63. package/src/index.ts +14 -0
  64. package/src/packlets/fs/index.ts +114 -0
  65. package/src/packlets/jsonc/index.ts +134 -0
  66. package/src/packlets/manifest/index.ts +29 -0
  67. package/src/packlets/manifest/types.ts +36 -0
  68. package/src/packlets/template/index.ts +48 -0
  69. package/sync-manifest.json +222 -0
  70. package/temp/build/typescript/ts_l9Fw4VUO.json +1 -0
  71. package/templates/.gitignore.tmpl +85 -0
  72. package/templates/ACTIVE_DEVELOPMENT.md.tmpl +58 -0
  73. package/templates/CLAUDE.md.tmpl +124 -0
  74. package/templates/command-line.json.tmpl +50 -0
  75. package/templates/package.json.tmpl +5 -0
  76. package/templates/version-policies.json.tmpl +8 -0
  77. package/tsconfig.json +7 -0
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ /**
3
+ * Template rendering — simple variable substitution for .tmpl files.
4
+ */
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.renderTemplate = renderTemplate;
40
+ exports.renderTemplateFile = renderTemplateFile;
41
+ exports.getDefaultTemplatesDir = getDefaultTemplatesDir;
42
+ const fs = __importStar(require("fs"));
43
+ const path = __importStar(require("path"));
44
+ /**
45
+ * Render a template string by replacing `{{VAR_NAME}}` placeholders.
46
+ */
47
+ function renderTemplate(template, vars) {
48
+ let result = template;
49
+ for (const [key, value] of Object.entries(vars)) {
50
+ result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value);
51
+ }
52
+ return result;
53
+ }
54
+ /**
55
+ * Read a template file, render it with variables, and write to destination.
56
+ */
57
+ function renderTemplateFile(templatePath, destPath, vars) {
58
+ if (!fs.existsSync(templatePath)) {
59
+ console.warn(` WARNING: Template not found, skipping: ${templatePath}`);
60
+ return;
61
+ }
62
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
63
+ const template = fs.readFileSync(templatePath, 'utf-8');
64
+ const rendered = renderTemplate(template, vars);
65
+ fs.writeFileSync(destPath, rendered);
66
+ console.log(` Generated: ${destPath}`);
67
+ }
68
+ /**
69
+ * Resolve the default templates directory relative to this tool's location.
70
+ */
71
+ function getDefaultTemplatesDir() {
72
+ const toolRoot = path.resolve(__dirname, '..', '..', '..');
73
+ return path.join(toolRoot, 'templates');
74
+ }
75
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/template/index.ts"],"names":[],"mappings":";AAAA;;GAEG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAeH,wCAMC;AAKD,gDAWC;AAKD,wDAGC;AA3CD,uCAAyB;AACzB,2CAA6B;AAS7B;;GAEG;AACH,SAAgB,cAAc,CAAC,QAAgB,EAAE,IAAmB;IAClE,IAAI,MAAM,GAAG,QAAQ,CAAC;IACtB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAChD,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,SAAS,GAAG,QAAQ,EAAE,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;IACxE,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,SAAgB,kBAAkB,CAAC,YAAoB,EAAE,QAAgB,EAAE,IAAmB;IAC5F,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QACjC,OAAO,CAAC,IAAI,CAAC,4CAA4C,YAAY,EAAE,CAAC,CAAC;QACzE,OAAO;IACT,CAAC;IAED,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1D,MAAM,QAAQ,GAAG,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IACxD,MAAM,QAAQ,GAAG,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAChD,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACrC,OAAO,CAAC,GAAG,CAAC,gBAAgB,QAAQ,EAAE,CAAC,CAAC;AAC1C,CAAC;AAED;;GAEG;AACH,SAAgB,sBAAsB;IACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAC3D,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;AAC1C,CAAC","sourcesContent":["/**\n * Template rendering — simple variable substitution for .tmpl files.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nexport interface ITemplateVars {\n REPO_URL: string;\n VERSION_POLICY_NAME: string;\n VERSION: string;\n [key: string]: string;\n}\n\n/**\n * Render a template string by replacing `{{VAR_NAME}}` placeholders.\n */\nexport function renderTemplate(template: string, vars: ITemplateVars): string {\n let result = template;\n for (const [key, value] of Object.entries(vars)) {\n result = result.replace(new RegExp(`\\\\{\\\\{${key}\\\\}\\\\}`, 'g'), value);\n }\n return result;\n}\n\n/**\n * Read a template file, render it with variables, and write to destination.\n */\nexport function renderTemplateFile(templatePath: string, destPath: string, vars: ITemplateVars): void {\n if (!fs.existsSync(templatePath)) {\n console.warn(` WARNING: Template not found, skipping: ${templatePath}`);\n return;\n }\n\n fs.mkdirSync(path.dirname(destPath), { recursive: true });\n const template = fs.readFileSync(templatePath, 'utf-8');\n const rendered = renderTemplate(template, vars);\n fs.writeFileSync(destPath, rendered);\n console.log(` Generated: ${destPath}`);\n}\n\n/**\n * Resolve the default templates directory relative to this tool's location.\n */\nexport function getDefaultTemplatesDir(): string {\n const toolRoot = path.resolve(__dirname, '..', '..', '..');\n return path.join(toolRoot, 'templates');\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@fgv/repo-template",
3
+ "version": "5.1.0-2",
4
+ "description": "CLI tool for creating and maintaining fgv-derived Rush monorepos",
5
+ "license": "MIT",
6
+ "main": "lib/index.js",
7
+ "bin": {
8
+ "repo-template": "bin/repo-template.js"
9
+ },
10
+ "scripts": {
11
+ "build": "heft build --clean",
12
+ "test": "heft test --clean",
13
+ "coverage": "heft test --clean --coverage",
14
+ "clean": "heft clean",
15
+ "lint": "eslint src --ext .ts",
16
+ "fixlint": "eslint src --ext .ts --fix"
17
+ },
18
+ "dependencies": {
19
+ "commander": "^11.0.0",
20
+ "jsonc-parser": "~3.3.1"
21
+ },
22
+ "devDependencies": {
23
+ "@rushstack/heft": "1.2.7",
24
+ "@rushstack/heft-node-rig": "2.11.27",
25
+ "@types/node": "^20.14.9",
26
+ "typescript": "5.9.3"
27
+ },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/ErikFortune/fgv.git"
31
+ }
32
+ }
@@ -0,0 +1,4 @@
1
+ This project was not found in the build cache.
2
+ Caching build output folders: lib, temp, .rush/temp/operation/build
3
+ Successfully set cache entry.
4
+ Cache key: 92e27a75687fa5062b71fdb897f0928a8aa4e0c9
@@ -0,0 +1,7 @@
1
+ Invoking: heft build --clean
2
+ ---- build started ----
3
+ [build:clean] Deleted 0 files and 2 folders
4
+ [build:typescript] The TypeScript compiler version 5.9.3 is newer than the latest version that was tested with Heft (5.8); it may not work correctly.
5
+ [build:typescript] Using TypeScript version 5.9.3
6
+ ---- build finished (0.796s) ----
7
+ -------------------- Finished (0.797s) --------------------
package/src/cli.ts ADDED
@@ -0,0 +1,141 @@
1
+ /**
2
+ * CLI entry point — sets up commander with create, sync, and patch subcommands.
3
+ */
4
+
5
+ import { Command } from 'commander';
6
+ import { detectSourceDir } from './packlets/fs';
7
+ import { runCreate } from './commands/create';
8
+ import { runSync } from './commands/sync';
9
+ import { runPatch, parsePatchArgs } from './commands/patch';
10
+ import { runInitLibrary, RigType, CategoryType } from './commands/init-library';
11
+
12
+ export class RepoTemplateCli {
13
+ private readonly _program: Command;
14
+
15
+ public constructor() {
16
+ this._program = new Command();
17
+ this._setupCommands();
18
+ }
19
+
20
+ public async run(argv: string[]): Promise<void> {
21
+ await this._program.parseAsync(argv);
22
+ }
23
+
24
+ private _setupCommands(): void {
25
+ this._program
26
+ .name('repo-template')
27
+ .description('Create and maintain fgv-derived Rush monorepos')
28
+ .version('5.1.0');
29
+
30
+ // ── create ──
31
+ this._program
32
+ .command('create')
33
+ .description('Stamp out a new fgv-derived Rush monorepo using rush init + JSONC patching')
34
+ .requiredOption('-t, --target-dir <path>', 'Directory to create the new repo in')
35
+ .requiredOption('-u, --repo-url <url>', 'GitHub repository URL')
36
+ .option('-p, --version-policy <name>', 'Version policy name', 'default')
37
+ .option('--initial-version <ver>', 'Initial version', '0.1.0')
38
+ .option('-s, --source-dir <path>', 'Source repo for shared files (auto-detected if in fgv repo)')
39
+ .option('--allow-existing', 'Allow target directory to already exist', false)
40
+ .option('--no-git-init', 'Skip git init and initial commit')
41
+ .action(async (opts) => {
42
+ const sourceDir = opts.sourceDir ?? this._resolveSourceDir();
43
+ await runCreate({
44
+ targetDir: opts.targetDir,
45
+ repoUrl: opts.repoUrl,
46
+ versionPolicy: opts.versionPolicy,
47
+ version: opts.initialVersion,
48
+ sourceDir,
49
+ allowExisting: opts.allowExisting,
50
+ gitInit: opts.gitInit
51
+ });
52
+ });
53
+
54
+ // ── sync ──
55
+ this._program
56
+ .command('sync')
57
+ .description('Sync shared files from the fgv source repo to a consumer repo')
58
+ .requiredOption('-t, --target-dir <path>', 'Consumer repo to update')
59
+ .option('-s, --source-dir <path>', 'Source repo (auto-detected if in fgv repo)')
60
+ .option('-n, --dry-run', 'Show what would change without modifying files', false)
61
+ .action(async (opts) => {
62
+ const sourceDir = opts.sourceDir ?? this._resolveSourceDir();
63
+ await runSync({
64
+ targetDir: opts.targetDir,
65
+ sourceDir,
66
+ dryRun: opts.dryRun
67
+ });
68
+ });
69
+
70
+ // ── init-library ──
71
+ this._program
72
+ .command('init-library')
73
+ .description('Scaffold a new library package within an existing Rush monorepo')
74
+ .requiredOption('-n, --name <name>', 'Package name (e.g. "ts-my-lib" — auto-prefixed with @fgv/)')
75
+ .option('-d, --description <text>', 'Package description', '')
76
+ .option('-r, --rig <type>', 'Heft rig: dual (default), node, or browser', 'dual')
77
+ .option(
78
+ '-c, --category <type>',
79
+ 'Category folder: libraries (default), tools, apps, services',
80
+ 'libraries'
81
+ )
82
+ .option('--repo-dir <path>', 'Rush monorepo root (default: cwd)', process.cwd())
83
+ .option('-p, --version-policy <name>', 'Version policy name', 'default')
84
+ .option('--initial-version <ver>', 'Initial version', '0.1.0')
85
+ .option(
86
+ '--fgv-dep-version <ver>',
87
+ 'Version spec for @fgv/* deps ("workspace:*" in fgv, version range in consumers)',
88
+ 'workspace:*'
89
+ )
90
+ .action(async (opts) => {
91
+ await runInitLibrary({
92
+ name: opts.name,
93
+ description: opts.description,
94
+ rig: opts.rig as RigType,
95
+ category: opts.category as CategoryType,
96
+ repoDir: opts.repoDir,
97
+ versionPolicy: opts.versionPolicy,
98
+ version: opts.initialVersion,
99
+ fgvDepVersion: opts.fgvDepVersion
100
+ });
101
+ });
102
+
103
+ // ── patch ──
104
+ this._program
105
+ .command('patch <file>')
106
+ .description('Apply targeted edits to a JSONC config file while preserving comments')
107
+ .allowUnknownOption(true)
108
+ .helpOption(false)
109
+ .action(async (file, _opts, cmd) => {
110
+ // Parse the raw args after the file argument as patch operations
111
+ const rawArgs = cmd.args.slice(1); // skip the file arg
112
+ // Actually, commander passes remaining args differently. Let's get them from process.argv
113
+ const allArgs = process.argv;
114
+ const patchIdx = allArgs.indexOf('patch');
115
+ const fileIdx = patchIdx + 1;
116
+ const opArgs = allArgs.slice(fileIdx + 1);
117
+
118
+ const operations = parsePatchArgs(opArgs);
119
+ if (operations.length === 0) {
120
+ console.error('No operations specified. Use --set, --uncomment, --add-to-array, etc.');
121
+ process.exit(1);
122
+ }
123
+ await runPatch({ file, operations });
124
+ });
125
+ }
126
+
127
+ /**
128
+ * Auto-detect the fgv source directory by walking up from the current working directory.
129
+ */
130
+ private _resolveSourceDir(): string {
131
+ const detected = detectSourceDir(process.cwd());
132
+ if (!detected) {
133
+ console.error(
134
+ 'ERROR: Could not auto-detect fgv source repo. ' +
135
+ 'Please specify --source-dir or run from within the fgv repository.'
136
+ );
137
+ process.exit(1);
138
+ }
139
+ return detected;
140
+ }
141
+ }
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Create command — stamps out a new fgv-derived Rush monorepo.
3
+ *
4
+ * Uses `rush init` to generate base config with full documentation,
5
+ * then applies fgv-specific customizations via JSONC patching and shared file sync.
6
+ */
7
+
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+ import { loadManifest, getDefaultManifestPath } from '../packlets/manifest';
11
+ import { patchFile, IPatchOperation } from '../packlets/jsonc';
12
+ import { renderTemplateFile, getDefaultTemplatesDir, ITemplateVars } from '../packlets/template';
13
+ import { copyFile, copyPackage, exec, getGitCommit, getGitRemoteUrl } from '../packlets/fs';
14
+
15
+ export interface ICreateOptions {
16
+ targetDir: string;
17
+ repoUrl: string;
18
+ versionPolicy: string;
19
+ version: string;
20
+ sourceDir: string;
21
+ allowExisting: boolean;
22
+ gitInit: boolean;
23
+ }
24
+
25
+ const RUSH_VERSION = '5.172.1';
26
+
27
+ const NODE_VERSION_RANGE =
28
+ '>=14.15.0 <15.0.0 || >=16.13.0 <17.0.0 || >=18.15.0 <19.0.0 || >=20.18.0 <21.0.0 || >=22.22.0 <23.0.0';
29
+
30
+ export async function runCreate(options: ICreateOptions): Promise<void> {
31
+ const { targetDir, repoUrl, versionPolicy, version, sourceDir, allowExisting, gitInit } = options;
32
+
33
+ // Validate
34
+ if (!allowExisting && fs.existsSync(targetDir)) {
35
+ throw new Error(`Target directory already exists: ${targetDir} (use --allow-existing to override)`);
36
+ }
37
+ if (!fs.existsSync(path.join(sourceDir, 'rush.json'))) {
38
+ throw new Error(`Source directory is not a Rush repo: ${sourceDir}`);
39
+ }
40
+
41
+ const manifestPath = getDefaultManifestPath();
42
+ const templatesDir = getDefaultTemplatesDir();
43
+ const manifest = loadManifest(manifestPath);
44
+
45
+ console.log(`Creating new monorepo at: ${targetDir}`);
46
+ console.log(` Source repo: ${sourceDir}`);
47
+ console.log(` Repo URL: ${repoUrl}`);
48
+ console.log(` Version: ${versionPolicy}@${version}`);
49
+ console.log('');
50
+
51
+ fs.mkdirSync(targetDir, { recursive: true });
52
+
53
+ // ── Step 1: rush init ──
54
+ if (fs.existsSync(path.join(targetDir, 'rush.json'))) {
55
+ console.log('==> Target already has rush.json, skipping rush init');
56
+ } else {
57
+ console.log('==> Running rush init...');
58
+ try {
59
+ const output = exec(`npx "@microsoft/rush@${RUSH_VERSION}" init`, { cwd: targetDir });
60
+ for (const line of output.split('\n')) {
61
+ console.log(` ${line}`);
62
+ }
63
+ } catch (err: unknown) {
64
+ // rush init writes to stderr for some messages, check if rush.json was created
65
+ if (!fs.existsSync(path.join(targetDir, 'rush.json'))) {
66
+ throw new Error(`rush init failed: ${(err as Error).message}`);
67
+ }
68
+ }
69
+ }
70
+
71
+ // ── Step 2: Patch rush.json ──
72
+ console.log('');
73
+ console.log('==> Patching rush.json with fgv customizations...');
74
+
75
+ const rushJsonOps: IPatchOperation[] = [
76
+ { type: 'set', path: 'nodeSupportedVersionRange', value: NODE_VERSION_RANGE },
77
+ { type: 'set', path: 'ensureConsistentVersions', value: true },
78
+ { type: 'uncomment', path: 'projectFolderMinDepth' },
79
+ { type: 'set', path: 'projectFolderMinDepth', value: 2 },
80
+ { type: 'uncomment', path: 'projectFolderMaxDepth' },
81
+ { type: 'set', path: 'projectFolderMaxDepth', value: 2 },
82
+ { type: 'uncomment', path: 'url' },
83
+ { type: 'set', path: 'repository.url', value: repoUrl },
84
+ { type: 'uncomment', path: 'defaultBranch' },
85
+ { type: 'uncomment', path: 'defaultRemote' }
86
+ ];
87
+
88
+ patchFile(path.join(targetDir, 'rush.json'), rushJsonOps);
89
+ for (const op of rushJsonOps) {
90
+ const desc =
91
+ op.type === 'uncomment' || op.type === 'remove'
92
+ ? ` ${op.type}: ${op.path}`
93
+ : ` ${op.type}: ${op.path}`;
94
+ console.log(desc);
95
+ }
96
+
97
+ // ── Step 3: Patch other rush config files ──
98
+ console.log('');
99
+ console.log('==> Patching rush config files...');
100
+
101
+ patchFile(path.join(targetDir, 'common/config/rush/build-cache.json'), [
102
+ { type: 'set', path: 'buildCacheEnabled', value: true }
103
+ ]);
104
+ console.log(' build-cache.json: buildCacheEnabled = true');
105
+
106
+ patchFile(path.join(targetDir, 'common/config/rush/pnpm-config.json'), [
107
+ { type: 'uncomment', path: 'strictPeerDependencies' },
108
+ { type: 'set', path: 'strictPeerDependencies', value: true }
109
+ ]);
110
+ console.log(' pnpm-config.json: strictPeerDependencies = true');
111
+
112
+ // ── Step 4: Generate templated files ──
113
+ console.log('');
114
+ console.log('==> Generating templated config files...');
115
+
116
+ const templateVars: ITemplateVars = {
117
+ REPO_URL: repoUrl,
118
+ VERSION_POLICY_NAME: versionPolicy,
119
+ VERSION: version
120
+ };
121
+
122
+ for (const tmpl of manifest.templated.files) {
123
+ const templatePath = path.join(sourceDir, tmpl.template);
124
+ const destPath = path.join(targetDir, tmpl.destination);
125
+ renderTemplateFile(templatePath, destPath, templateVars);
126
+ }
127
+
128
+ // ── Step 5: Copy shared files ──
129
+ console.log('');
130
+ console.log('==> Copying shared files...');
131
+
132
+ for (const file of manifest.shared.files) {
133
+ const srcPath = path.join(sourceDir, file.source);
134
+ const destPath = path.join(targetDir, file.destination);
135
+ if (!fs.existsSync(srcPath)) {
136
+ console.warn(` WARNING: Source file not found, skipping: ${file.source}`);
137
+ continue;
138
+ }
139
+ copyFile(srcPath, destPath);
140
+ console.log(` Copied: ${file.destination}`);
141
+ }
142
+
143
+ for (const pkg of manifest.sharedPackages.packages) {
144
+ const srcPath = path.join(sourceDir, pkg.source);
145
+ const destPath = path.join(targetDir, pkg.destination);
146
+ if (!fs.existsSync(srcPath)) {
147
+ console.warn(` WARNING: Source directory not found, skipping: ${pkg.source}`);
148
+ continue;
149
+ }
150
+ copyPackage(srcPath, destPath);
151
+ console.log(` Copied package: ${pkg.destination}`);
152
+ }
153
+
154
+ // ── Step 6: Create standard directories ──
155
+ console.log('');
156
+ console.log('==> Creating directory structure...');
157
+
158
+ const dirs = ['libraries', 'tools', 'apps', 'services', '.claude/project', '.claude/skills'];
159
+ for (const dir of dirs) {
160
+ fs.mkdirSync(path.join(targetDir, dir), { recursive: true });
161
+ }
162
+
163
+ const gitkeeps = ['.claude/project/.gitkeep', '.claude/skills/.gitkeep'];
164
+ for (const gk of gitkeeps) {
165
+ fs.writeFileSync(path.join(targetDir, gk), '');
166
+ }
167
+ console.log(' Created standard directory structure');
168
+
169
+ // ── Step 7: Record template metadata ──
170
+ console.log('');
171
+ console.log('==> Recording template metadata...');
172
+
173
+ const sourceCommit = getGitCommit(sourceDir);
174
+ const sourceRepo = getGitRemoteUrl(sourceDir);
175
+
176
+ const syncMetadata = {
177
+ templateSource: repoUrl,
178
+ sourceRepo,
179
+ sourceCommit,
180
+ createdAt: new Date().toISOString(),
181
+ lastSyncedAt: new Date().toISOString(),
182
+ manifestVersion: '1.0.0'
183
+ };
184
+ fs.writeFileSync(path.join(targetDir, '.template-sync'), JSON.stringify(syncMetadata, null, 2) + '\n');
185
+ console.log(' Created .template-sync');
186
+
187
+ // ── Step 8: Git init ──
188
+ if (gitInit) {
189
+ console.log('');
190
+ console.log('==> Initializing git repository...');
191
+ try {
192
+ exec('git init -b main', { cwd: targetDir });
193
+ exec('git add -A', { cwd: targetDir });
194
+ exec(
195
+ `git commit -m "Initial commit from fgv monorepo template\n\nSource: ${sourceDir}\nTemplate commit: ${sourceCommit}"`,
196
+ { cwd: targetDir }
197
+ );
198
+ console.log(' Git repository initialized with initial commit');
199
+ } catch (err: unknown) {
200
+ console.warn(` WARNING: Git init failed: ${(err as Error).message}`);
201
+ }
202
+ }
203
+
204
+ // ── Done ──
205
+ console.log('');
206
+ console.log(`=== Monorepo created successfully at: ${targetDir} ===`);
207
+ console.log('');
208
+ console.log('Next steps:');
209
+ console.log(' 1. Add your domain packages to libraries/, apps/, tools/, services/');
210
+ console.log(' 2. Register them in rush.json');
211
+ console.log(' 3. Fill in the project table in CLAUDE.md');
212
+ console.log(' 4. Fill in ACTIVE_DEVELOPMENT.md with your domain projects');
213
+ console.log(' 5. Add domain-specific Rush commands to common/config/rush/command-line.json');
214
+ console.log(` 6. Run: cd ${targetDir} && rush install && rush build`);
215
+ console.log('');
216
+ }