@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.
- package/.rush/temp/9cc3c12fcd90dc622d9e260e68873bfe822f80a1.tar.log +61 -0
- package/.rush/temp/chunked-rush-logs/repo-template.build.chunks.jsonl +6 -0
- package/.rush/temp/operation/build/all.log +6 -0
- package/.rush/temp/operation/build/log-chunks.jsonl +6 -0
- package/.rush/temp/operation/build/state.json +3 -0
- package/.rush/temp/shrinkwrap-deps.json +576 -0
- package/README.md +216 -0
- package/bin/repo-template.js +18 -0
- package/config/rig.json +4 -0
- package/lib/cli.d.ts +14 -0
- package/lib/cli.d.ts.map +1 -0
- package/lib/cli.js +167 -0
- package/lib/cli.js.map +1 -0
- package/lib/commands/create.d.ts +17 -0
- package/lib/commands/create.d.ts.map +1 -0
- package/lib/commands/create.js +212 -0
- package/lib/commands/create.js.map +1 -0
- package/lib/commands/init-library.d.ts +38 -0
- package/lib/commands/init-library.d.ts.map +1 -0
- package/lib/commands/init-library.js +269 -0
- package/lib/commands/init-library.js.map +1 -0
- package/lib/commands/link.d.ts +20 -0
- package/lib/commands/link.d.ts.map +1 -0
- package/lib/commands/link.js +273 -0
- package/lib/commands/link.js.map +1 -0
- package/lib/commands/patch.d.ts +15 -0
- package/lib/commands/patch.d.ts.map +1 -0
- package/lib/commands/patch.js +104 -0
- package/lib/commands/patch.js.map +1 -0
- package/lib/commands/sync.d.ts +11 -0
- package/lib/commands/sync.d.ts.map +1 -0
- package/lib/commands/sync.js +156 -0
- package/lib/commands/sync.js.map +1 -0
- package/lib/index.d.ts +15 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +33 -0
- package/lib/index.js.map +1 -0
- package/lib/packlets/fs/index.d.ts +40 -0
- package/lib/packlets/fs/index.d.ts.map +1 -0
- package/lib/packlets/fs/index.js +142 -0
- package/lib/packlets/fs/index.js.map +1 -0
- package/lib/packlets/jsonc/index.d.ts +27 -0
- package/lib/packlets/jsonc/index.d.ts.map +1 -0
- package/lib/packlets/jsonc/index.js +124 -0
- package/lib/packlets/jsonc/index.js.map +1 -0
- package/lib/packlets/manifest/index.d.ts +15 -0
- package/lib/packlets/manifest/index.d.ts.map +1 -0
- package/lib/packlets/manifest/index.js +61 -0
- package/lib/packlets/manifest/index.js.map +1 -0
- package/lib/packlets/manifest/types.d.ts +33 -0
- package/lib/packlets/manifest/types.d.ts.map +1 -0
- package/lib/packlets/manifest/types.js +6 -0
- package/lib/packlets/manifest/types.js.map +1 -0
- package/lib/packlets/template/index.d.ts +22 -0
- package/lib/packlets/template/index.d.ts.map +1 -0
- package/lib/packlets/template/index.js +75 -0
- package/lib/packlets/template/index.js.map +1 -0
- package/package.json +32 -0
- package/rush-logs/repo-template.build.cache.log +5 -0
- package/rush-logs/repo-template.build.log +6 -0
- package/src/cli.ts +197 -0
- package/src/commands/create.ts +216 -0
- package/src/commands/init-library.ts +313 -0
- package/src/commands/link.ts +299 -0
- package/src/commands/patch.ts +84 -0
- package/src/commands/sync.ts +137 -0
- package/src/index.ts +22 -0
- package/src/packlets/fs/index.ts +114 -0
- package/src/packlets/jsonc/index.ts +134 -0
- package/src/packlets/manifest/index.ts +29 -0
- package/src/packlets/manifest/types.ts +36 -0
- package/src/packlets/template/index.ts +48 -0
- package/sync-manifest.json +222 -0
- package/temp/build/typescript/ts_l9Fw4VUO.json +1 -0
- package/templates/.gitignore.tmpl +85 -0
- package/templates/ACTIVE_DEVELOPMENT.md.tmpl +58 -0
- package/templates/CLAUDE.md.tmpl +124 -0
- package/templates/command-line.json.tmpl +50 -0
- package/templates/package.json.tmpl +5 -0
- package/templates/version-policies.json.tmpl +8 -0
- package/tsconfig.json +7 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync command — updates shared files in a consumer repo from the template source.
|
|
3
|
+
* Templated files are NOT touched — they are owned by the consumer after creation.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import { loadManifest, getDefaultManifestPath } from '../packlets/manifest';
|
|
9
|
+
import { copyFile, copyPackage, filesAreEqual, getGitCommit } from '../packlets/fs';
|
|
10
|
+
|
|
11
|
+
export interface ISyncOptions {
|
|
12
|
+
targetDir: string;
|
|
13
|
+
sourceDir: string;
|
|
14
|
+
dryRun: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function runSync(options: ISyncOptions): Promise<void> {
|
|
18
|
+
const { targetDir, sourceDir, dryRun } = options;
|
|
19
|
+
|
|
20
|
+
if (!fs.existsSync(targetDir)) {
|
|
21
|
+
throw new Error(`Target directory does not exist: ${targetDir}`);
|
|
22
|
+
}
|
|
23
|
+
if (!fs.existsSync(path.join(sourceDir, 'rush.json'))) {
|
|
24
|
+
throw new Error(`Source directory is not a Rush repo: ${sourceDir}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const manifestPath = getDefaultManifestPath();
|
|
28
|
+
const manifest = loadManifest(manifestPath);
|
|
29
|
+
|
|
30
|
+
console.log('Syncing shared files');
|
|
31
|
+
console.log(` Source: ${sourceDir}`);
|
|
32
|
+
console.log(` Target: ${targetDir}`);
|
|
33
|
+
if (dryRun) {
|
|
34
|
+
console.log(' Mode: DRY RUN (no changes will be made)');
|
|
35
|
+
}
|
|
36
|
+
console.log('');
|
|
37
|
+
|
|
38
|
+
let copied = 0;
|
|
39
|
+
let skipped = 0;
|
|
40
|
+
let warnings = 0;
|
|
41
|
+
|
|
42
|
+
// ── Sync individual shared files ──
|
|
43
|
+
console.log('==> Syncing shared files...');
|
|
44
|
+
|
|
45
|
+
for (const file of manifest.shared.files) {
|
|
46
|
+
const srcPath = path.join(sourceDir, file.source);
|
|
47
|
+
const dstPath = path.join(targetDir, file.destination);
|
|
48
|
+
|
|
49
|
+
if (!fs.existsSync(srcPath)) {
|
|
50
|
+
console.log(` WARNING: Source not found: ${file.source}`);
|
|
51
|
+
warnings++;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (dryRun) {
|
|
56
|
+
if (fs.existsSync(dstPath) && filesAreEqual(srcPath, dstPath)) {
|
|
57
|
+
console.log(` [unchanged] ${file.destination}`);
|
|
58
|
+
skipped++;
|
|
59
|
+
} else if (fs.existsSync(dstPath)) {
|
|
60
|
+
console.log(` [would update] ${file.destination}`);
|
|
61
|
+
copied++;
|
|
62
|
+
} else {
|
|
63
|
+
console.log(` [would create] ${file.destination}`);
|
|
64
|
+
copied++;
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
if (fs.existsSync(dstPath) && filesAreEqual(srcPath, dstPath)) {
|
|
68
|
+
skipped++;
|
|
69
|
+
} else {
|
|
70
|
+
copyFile(srcPath, dstPath);
|
|
71
|
+
console.log(` Updated: ${file.destination}`);
|
|
72
|
+
copied++;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ── Sync shared packages ──
|
|
78
|
+
console.log('');
|
|
79
|
+
console.log('==> Syncing shared packages...');
|
|
80
|
+
|
|
81
|
+
for (const pkg of manifest.sharedPackages.packages) {
|
|
82
|
+
const srcPath = path.join(sourceDir, pkg.source);
|
|
83
|
+
const dstPath = path.join(targetDir, pkg.destination);
|
|
84
|
+
|
|
85
|
+
if (!fs.existsSync(srcPath)) {
|
|
86
|
+
console.log(` WARNING: Source directory not found: ${pkg.source}`);
|
|
87
|
+
warnings++;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (dryRun) {
|
|
92
|
+
console.log(` [would sync] ${pkg.destination}/`);
|
|
93
|
+
copied++;
|
|
94
|
+
} else {
|
|
95
|
+
copyPackage(srcPath, dstPath);
|
|
96
|
+
console.log(` Synced package: ${pkg.destination}`);
|
|
97
|
+
copied++;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ── Update sync metadata ──
|
|
102
|
+
if (!dryRun) {
|
|
103
|
+
const syncFilePath = path.join(targetDir, '.template-sync');
|
|
104
|
+
if (fs.existsSync(syncFilePath)) {
|
|
105
|
+
try {
|
|
106
|
+
const syncData = JSON.parse(fs.readFileSync(syncFilePath, 'utf-8'));
|
|
107
|
+
syncData.lastSyncedAt = new Date().toISOString();
|
|
108
|
+
syncData.sourceCommit = getGitCommit(sourceDir);
|
|
109
|
+
fs.writeFileSync(syncFilePath, JSON.stringify(syncData, null, 2) + '\n');
|
|
110
|
+
console.log('');
|
|
111
|
+
console.log(' Updated .template-sync');
|
|
112
|
+
} catch {
|
|
113
|
+
console.log(' WARNING: Could not update .template-sync');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ── Summary ──
|
|
119
|
+
console.log('');
|
|
120
|
+
console.log('=== Sync complete ===');
|
|
121
|
+
console.log(` Updated: ${copied}`);
|
|
122
|
+
console.log(` Unchanged: ${skipped}`);
|
|
123
|
+
console.log(` Warnings: ${warnings}`);
|
|
124
|
+
|
|
125
|
+
if (dryRun) {
|
|
126
|
+
console.log('');
|
|
127
|
+
console.log('This was a dry run. No files were modified.');
|
|
128
|
+
console.log('Run without --dry-run to apply changes.');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!dryRun && copied > 0) {
|
|
132
|
+
console.log('');
|
|
133
|
+
console.log('Next steps:');
|
|
134
|
+
console.log(` 1. Review changes: cd ${targetDir} && git diff`);
|
|
135
|
+
console.log(" 2. Commit: git add -A && git commit -m 'Sync shared files from template'");
|
|
136
|
+
}
|
|
137
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fgv/repo-template — CLI tool for creating and maintaining fgv-derived Rush monorepos.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { RepoTemplateCli } from './cli';
|
|
8
|
+
export { runCreate, ICreateOptions } from './commands/create';
|
|
9
|
+
export { runSync, ISyncOptions } from './commands/sync';
|
|
10
|
+
export { runPatch, IPatchOptions, parsePatchArgs } from './commands/patch';
|
|
11
|
+
export { runInitLibrary, IInitLibraryOptions, RigType, CategoryType } from './commands/init-library';
|
|
12
|
+
export {
|
|
13
|
+
runLink,
|
|
14
|
+
runUnlink,
|
|
15
|
+
runUpdateFgvVersions,
|
|
16
|
+
ILinkOptions,
|
|
17
|
+
IUnlinkOptions,
|
|
18
|
+
IUpdateFgvVersionsOptions
|
|
19
|
+
} from './commands/link';
|
|
20
|
+
export { applyOperations, applyOperation, patchFile, IPatchOperation } from './packlets/jsonc';
|
|
21
|
+
export { loadManifest, IManifest, ISharedFile, ISharedPackage, ITemplatedFile } from './packlets/manifest';
|
|
22
|
+
export { renderTemplate, renderTemplateFile, ITemplateVars } from './packlets/template';
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File system and shell execution helpers.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import { execSync, ExecSyncOptions } from 'child_process';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Directories to exclude when copying shared packages.
|
|
11
|
+
*/
|
|
12
|
+
const EXCLUDED_DIRS = new Set(['node_modules', 'lib', 'dist', '.heft', 'temp', 'coverage', 'rush-logs']);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Copy a single file, creating parent directories as needed.
|
|
16
|
+
*/
|
|
17
|
+
export function copyFile(src: string, dest: string): void {
|
|
18
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
19
|
+
fs.copyFileSync(src, dest);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Copy a directory recursively, excluding build artifacts.
|
|
24
|
+
*/
|
|
25
|
+
export function copyPackage(src: string, dest: string): void {
|
|
26
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
27
|
+
fs.cpSync(src, dest, {
|
|
28
|
+
recursive: true,
|
|
29
|
+
filter: (source: string) => {
|
|
30
|
+
const basename = path.basename(source);
|
|
31
|
+
return !EXCLUDED_DIRS.has(basename);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Compare two files for equality.
|
|
38
|
+
* Returns true if files are identical.
|
|
39
|
+
*/
|
|
40
|
+
export function filesAreEqual(pathA: string, pathB: string): boolean {
|
|
41
|
+
if (!fs.existsSync(pathA) || !fs.existsSync(pathB)) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
const a = fs.readFileSync(pathA);
|
|
45
|
+
const b = fs.readFileSync(pathB);
|
|
46
|
+
return Buffer.compare(a, b) === 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Execute a shell command, returning stdout.
|
|
51
|
+
*/
|
|
52
|
+
export function exec(command: string, options?: ExecSyncOptions): string {
|
|
53
|
+
const result = execSync(command, {
|
|
54
|
+
encoding: 'utf-8',
|
|
55
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
56
|
+
...options
|
|
57
|
+
});
|
|
58
|
+
return (result as string).trim();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Execute a shell command, inheriting stdio (output goes to terminal).
|
|
63
|
+
*/
|
|
64
|
+
export function execInherit(command: string, options?: ExecSyncOptions): void {
|
|
65
|
+
execSync(command, {
|
|
66
|
+
stdio: 'inherit',
|
|
67
|
+
...options
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Try to get the current git commit hash from a directory.
|
|
73
|
+
* Returns empty string if not a git repo or git is not available.
|
|
74
|
+
*/
|
|
75
|
+
export function getGitCommit(dir: string): string {
|
|
76
|
+
try {
|
|
77
|
+
return exec('git rev-parse HEAD', { cwd: dir });
|
|
78
|
+
} catch {
|
|
79
|
+
return '';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Try to get the git remote URL from a directory.
|
|
85
|
+
*/
|
|
86
|
+
export function getGitRemoteUrl(dir: string): string {
|
|
87
|
+
try {
|
|
88
|
+
return exec('git remote get-url origin', { cwd: dir });
|
|
89
|
+
} catch {
|
|
90
|
+
return 'local';
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Auto-detect the fgv source repository root by walking up from a starting directory,
|
|
96
|
+
* looking for a rush.json that belongs to the fgv repo.
|
|
97
|
+
*/
|
|
98
|
+
export function detectSourceDir(startDir: string): string | undefined {
|
|
99
|
+
let dir = path.resolve(startDir);
|
|
100
|
+
const root = path.parse(dir).root;
|
|
101
|
+
|
|
102
|
+
while (dir !== root) {
|
|
103
|
+
const rushJsonPath = path.join(dir, 'rush.json');
|
|
104
|
+
if (fs.existsSync(rushJsonPath)) {
|
|
105
|
+
// Verify it looks like the fgv repo by checking for the template tool
|
|
106
|
+
const manifestPath = path.join(dir, 'tools', 'repo-template', 'sync-manifest.json');
|
|
107
|
+
if (fs.existsSync(manifestPath)) {
|
|
108
|
+
return dir;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
dir = path.dirname(dir);
|
|
112
|
+
}
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSONC patching operations using jsonc-parser.
|
|
3
|
+
* Modifies JSON-with-comments files while preserving all comments and formatting.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { modify, applyEdits } from 'jsonc-parser';
|
|
7
|
+
|
|
8
|
+
const FORMATTING_OPTIONS = {
|
|
9
|
+
tabSize: 2,
|
|
10
|
+
insertSpaces: true,
|
|
11
|
+
eol: '\n'
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type PatchOperationType = 'set' | 'set-json' | 'uncomment' | 'add-to-array' | 'remove';
|
|
15
|
+
|
|
16
|
+
export interface IPatchOperation {
|
|
17
|
+
type: PatchOperationType;
|
|
18
|
+
path: string;
|
|
19
|
+
value?: unknown;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Parse a dot-separated path into a JSON path array.
|
|
24
|
+
* Numeric segments are treated as array indices.
|
|
25
|
+
*/
|
|
26
|
+
function parsePath(pathStr: string): (string | number)[] {
|
|
27
|
+
return pathStr.split('.').map((seg) => {
|
|
28
|
+
const num = Number(seg);
|
|
29
|
+
return Number.isInteger(num) && num >= 0 ? num : seg;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Parse a value string into a JS value.
|
|
35
|
+
*/
|
|
36
|
+
export function parseValue(valueStr: string): unknown {
|
|
37
|
+
if (valueStr === 'true') return true;
|
|
38
|
+
if (valueStr === 'false') return false;
|
|
39
|
+
if (valueStr === 'null') return null;
|
|
40
|
+
const num = Number(valueStr);
|
|
41
|
+
if (!isNaN(num) && valueStr.trim() !== '') return num;
|
|
42
|
+
// Strip surrounding quotes if present
|
|
43
|
+
if (
|
|
44
|
+
(valueStr.startsWith('"') && valueStr.endsWith('"')) ||
|
|
45
|
+
(valueStr.startsWith("'") && valueStr.endsWith("'"))
|
|
46
|
+
) {
|
|
47
|
+
return valueStr.slice(1, -1);
|
|
48
|
+
}
|
|
49
|
+
return valueStr;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function escapeRegExp(str: string): string {
|
|
53
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Uncomment a commented-out JSON property in a JSONC string.
|
|
58
|
+
* Handles `// "propertyName": value` patterns.
|
|
59
|
+
*/
|
|
60
|
+
function uncommentProperty(source: string, propertyName: string): string {
|
|
61
|
+
const pattern = new RegExp(`^(\\s*)\\/\\/\\s*("${escapeRegExp(propertyName)}"\\s*:.*)$`, 'm');
|
|
62
|
+
|
|
63
|
+
const match = source.match(pattern);
|
|
64
|
+
if (!match) {
|
|
65
|
+
console.warn(` Warning: Could not find commented-out property "${propertyName}" to uncomment`);
|
|
66
|
+
return source;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const indent = match[1];
|
|
70
|
+
const content = match[2];
|
|
71
|
+
return source.replace(match[0], `${indent}${content}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Apply a single patch operation to a JSONC source string.
|
|
76
|
+
*/
|
|
77
|
+
export function applyOperation(source: string, op: IPatchOperation): string {
|
|
78
|
+
switch (op.type) {
|
|
79
|
+
case 'set': {
|
|
80
|
+
const jsonPath = parsePath(op.path);
|
|
81
|
+
const edits = modify(source, jsonPath, op.value, { formattingOptions: FORMATTING_OPTIONS });
|
|
82
|
+
return applyEdits(source, edits);
|
|
83
|
+
}
|
|
84
|
+
case 'set-json': {
|
|
85
|
+
const jsonPath = parsePath(op.path);
|
|
86
|
+
const value = JSON.parse(op.value as string);
|
|
87
|
+
const edits = modify(source, jsonPath, value, { formattingOptions: FORMATTING_OPTIONS });
|
|
88
|
+
return applyEdits(source, edits);
|
|
89
|
+
}
|
|
90
|
+
case 'uncomment': {
|
|
91
|
+
// Use the leaf property name for the regex, since nested properties
|
|
92
|
+
// appear as just their leaf name in the file.
|
|
93
|
+
const leafName = op.path.split('.').pop()!;
|
|
94
|
+
return uncommentProperty(source, leafName);
|
|
95
|
+
}
|
|
96
|
+
case 'add-to-array': {
|
|
97
|
+
const jsonPath = parsePath(op.path);
|
|
98
|
+
const value = JSON.parse(op.value as string);
|
|
99
|
+
const edits = modify(source, [...jsonPath, -1], value, {
|
|
100
|
+
formattingOptions: FORMATTING_OPTIONS,
|
|
101
|
+
isArrayInsertion: true
|
|
102
|
+
});
|
|
103
|
+
return applyEdits(source, edits);
|
|
104
|
+
}
|
|
105
|
+
case 'remove': {
|
|
106
|
+
const jsonPath = parsePath(op.path);
|
|
107
|
+
const edits = modify(source, jsonPath, undefined, { formattingOptions: FORMATTING_OPTIONS });
|
|
108
|
+
return applyEdits(source, edits);
|
|
109
|
+
}
|
|
110
|
+
default:
|
|
111
|
+
throw new Error(`Unknown operation type: ${(op as IPatchOperation).type}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Apply a sequence of patch operations to a JSONC source string.
|
|
117
|
+
*/
|
|
118
|
+
export function applyOperations(source: string, operations: IPatchOperation[]): string {
|
|
119
|
+
let result = source;
|
|
120
|
+
for (const op of operations) {
|
|
121
|
+
result = applyOperation(result, op);
|
|
122
|
+
}
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Read a JSONC file, apply patch operations, and write it back.
|
|
128
|
+
*/
|
|
129
|
+
export function patchFile(filePath: string, operations: IPatchOperation[]): void {
|
|
130
|
+
const fs = require('fs');
|
|
131
|
+
let source = fs.readFileSync(filePath, 'utf-8');
|
|
132
|
+
source = applyOperations(source, operations);
|
|
133
|
+
fs.writeFileSync(filePath, source);
|
|
134
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manifest loader — reads and parses sync-manifest.json.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import { IManifest } from './types';
|
|
8
|
+
|
|
9
|
+
export { IManifest, ISharedFile, ISharedPackage, ITemplatedFile } from './types';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Load and parse a sync-manifest.json file.
|
|
13
|
+
*/
|
|
14
|
+
export function loadManifest(manifestPath: string): IManifest {
|
|
15
|
+
const content = fs.readFileSync(manifestPath, 'utf-8');
|
|
16
|
+
return JSON.parse(content) as IManifest;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the default manifest path relative to this tool's location.
|
|
21
|
+
* Works whether running from source (src/) or compiled (lib/).
|
|
22
|
+
*/
|
|
23
|
+
export function getDefaultManifestPath(): string {
|
|
24
|
+
// Walk up from this file to the tool root, then find sync-manifest.json
|
|
25
|
+
// In compiled form: lib/packlets/manifest/index.js → ../../.. → tool root
|
|
26
|
+
// In source form: src/packlets/manifest/index.ts → ../../.. → tool root
|
|
27
|
+
const toolRoot = path.resolve(__dirname, '..', '..', '..');
|
|
28
|
+
return path.join(toolRoot, 'sync-manifest.json');
|
|
29
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the sync-manifest.json file.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface ISharedFile {
|
|
6
|
+
description: string;
|
|
7
|
+
source: string;
|
|
8
|
+
destination: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ISharedPackage {
|
|
12
|
+
description: string;
|
|
13
|
+
source: string;
|
|
14
|
+
destination: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ITemplatedFile {
|
|
18
|
+
description: string;
|
|
19
|
+
template: string;
|
|
20
|
+
destination: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface IManifest {
|
|
24
|
+
shared: {
|
|
25
|
+
description: string;
|
|
26
|
+
files: ISharedFile[];
|
|
27
|
+
};
|
|
28
|
+
sharedPackages: {
|
|
29
|
+
description: string;
|
|
30
|
+
packages: ISharedPackage[];
|
|
31
|
+
};
|
|
32
|
+
templated: {
|
|
33
|
+
description: string;
|
|
34
|
+
files: ITemplatedFile[];
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template rendering — simple variable substitution for .tmpl files.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
|
|
8
|
+
export interface ITemplateVars {
|
|
9
|
+
REPO_URL: string;
|
|
10
|
+
VERSION_POLICY_NAME: string;
|
|
11
|
+
VERSION: string;
|
|
12
|
+
[key: string]: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Render a template string by replacing `{{VAR_NAME}}` placeholders.
|
|
17
|
+
*/
|
|
18
|
+
export function renderTemplate(template: string, vars: ITemplateVars): string {
|
|
19
|
+
let result = template;
|
|
20
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
21
|
+
result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value);
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Read a template file, render it with variables, and write to destination.
|
|
28
|
+
*/
|
|
29
|
+
export function renderTemplateFile(templatePath: string, destPath: string, vars: ITemplateVars): void {
|
|
30
|
+
if (!fs.existsSync(templatePath)) {
|
|
31
|
+
console.warn(` WARNING: Template not found, skipping: ${templatePath}`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
36
|
+
const template = fs.readFileSync(templatePath, 'utf-8');
|
|
37
|
+
const rendered = renderTemplate(template, vars);
|
|
38
|
+
fs.writeFileSync(destPath, rendered);
|
|
39
|
+
console.log(` Generated: ${destPath}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Resolve the default templates directory relative to this tool's location.
|
|
44
|
+
*/
|
|
45
|
+
export function getDefaultTemplatesDir(): string {
|
|
46
|
+
const toolRoot = path.resolve(__dirname, '..', '..', '..');
|
|
47
|
+
return path.join(toolRoot, 'templates');
|
|
48
|
+
}
|