@garyr/pt-cli 0.25.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/README.md +112 -0
- package/dist/commands/addCommand.js +37 -0
- package/dist/commands/configCommand.js +96 -0
- package/dist/commands/ignoreCommand.js +12 -0
- package/dist/commands/initCommand.js +294 -0
- package/dist/commands/learnCommand.js +473 -0
- package/dist/commands/removeCommand.js +28 -0
- package/dist/commands/variablesCommand.js +62 -0
- package/dist/config.js +283 -0
- package/dist/index.js +75 -0
- package/dist/postconfig.js +73 -0
- package/dist/substitute.js +98 -0
- package/doc/configuration.md +333 -0
- package/doc/exclusions.md +35 -0
- package/doc/usage.md +119 -0
- package/doc/variable_substitution_example.md +78 -0
- package/package.json +36 -0
- package/skills/agency-pt-operator/SKILL.md +61 -0
- package/src/commands/addCommand.ts +41 -0
- package/src/commands/configCommand.ts +102 -0
- package/src/commands/ignoreCommand.ts +17 -0
- package/src/commands/initCommand.ts +311 -0
- package/src/commands/learnCommand.ts +492 -0
- package/src/commands/removeCommand.ts +35 -0
- package/src/commands/variablesCommand.ts +67 -0
- package/src/config.ts +356 -0
- package/src/index.ts +92 -0
- package/src/postconfig.ts +87 -0
- package/src/substitute.ts +122 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { loadConfig, saveConfig } from '../config.js';
|
|
5
|
+
|
|
6
|
+
export interface AddOptions {
|
|
7
|
+
file?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function addCommand(name: string, jsonStr: string | undefined, options: AddOptions = {}) {
|
|
11
|
+
const config = loadConfig();
|
|
12
|
+
try {
|
|
13
|
+
let data;
|
|
14
|
+
if (options.file) {
|
|
15
|
+
const filePath = path.resolve(options.file);
|
|
16
|
+
data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
17
|
+
} else if (jsonStr) {
|
|
18
|
+
data = JSON.parse(jsonStr);
|
|
19
|
+
} else {
|
|
20
|
+
console.error('Error: Either a JSON string or --file <path> must be provided.');
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!config.templates) config.templates = {};
|
|
25
|
+
|
|
26
|
+
// Basic validation: ensure we aren't accidentally adding a full config object
|
|
27
|
+
if (data && data.templates && typeof data.templates === 'object') {
|
|
28
|
+
console.error(chalk.red('Error: The provided JSON appears to be a full configuration file, not a single template.'));
|
|
29
|
+
console.error(chalk.gray('If you want to import a specific template from it, extract that template object first.'));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
config.templates[name] = data;
|
|
34
|
+
saveConfig(config);
|
|
35
|
+
console.log(chalk.green(`ā Template "${name}" saved successfully.`));
|
|
36
|
+
} catch (e) {
|
|
37
|
+
const error = e as Error;
|
|
38
|
+
console.error(chalk.red(`Failed to parse template JSON: ${error.message}`));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { loadConfig, getTemplateNames, CONFIG_PATH } from '../config.js';
|
|
3
|
+
|
|
4
|
+
export interface ConfigOptions {
|
|
5
|
+
json?: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function configCommand(templateName: string | undefined, options: ConfigOptions = {}) {
|
|
9
|
+
const config = loadConfig();
|
|
10
|
+
|
|
11
|
+
if (options.json) {
|
|
12
|
+
if (templateName) {
|
|
13
|
+
if (config.templates && config.templates[templateName]) {
|
|
14
|
+
const output = {
|
|
15
|
+
name: templateName,
|
|
16
|
+
...config.templates[templateName]
|
|
17
|
+
};
|
|
18
|
+
console.log(JSON.stringify(output, null, 2));
|
|
19
|
+
} else {
|
|
20
|
+
console.error(chalk.red(`Error: Template "${templateName}" not found.`));
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
} else {
|
|
24
|
+
console.log(JSON.stringify(config, null, 2));
|
|
25
|
+
}
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const names = getTemplateNames(config);
|
|
30
|
+
|
|
31
|
+
console.log(chalk.cyan('Config Location:'), CONFIG_PATH);
|
|
32
|
+
console.log(chalk.cyan('\nLearned Templates:'));
|
|
33
|
+
if (names.length === 0) {
|
|
34
|
+
console.log(chalk.gray(' (none)'));
|
|
35
|
+
} else {
|
|
36
|
+
for (const name of names) {
|
|
37
|
+
const t = config.templates[name];
|
|
38
|
+
if (!t) continue;
|
|
39
|
+
console.log(chalk.white(` - ${name}`), chalk.gray(`(${t.description})`));
|
|
40
|
+
if (t.templateRoot) {
|
|
41
|
+
console.log(chalk.gray(` Source: ${t.templateRoot}`));
|
|
42
|
+
}
|
|
43
|
+
if (t.post_config && t.post_config.length > 0) {
|
|
44
|
+
console.log(chalk.cyan(' Post-config:'));
|
|
45
|
+
for (const task of t.post_config) {
|
|
46
|
+
const cmd = task.command || task.script || '(unknown)';
|
|
47
|
+
const typeFilter = task.type ? ` [type: ${task.type}]` : '';
|
|
48
|
+
console.log(chalk.gray(` - ${cmd}${typeFilter}`));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (t.post_copy && t.post_copy.length > 0) {
|
|
52
|
+
console.log(chalk.cyan(' post_copy:'));
|
|
53
|
+
for (const f of t.post_copy) {
|
|
54
|
+
console.log(chalk.gray(` - ${f.src} ā ${(f.dest || f.src)}`));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Show global ignore patterns
|
|
61
|
+
if (config.ignore && config.ignore.length > 0) {
|
|
62
|
+
console.log(chalk.cyan('\nIgnore Patterns (pt learn):'));
|
|
63
|
+
for (const p of config.ignore) {
|
|
64
|
+
console.log(chalk.gray(` - ${p}`));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Show default post-config tasks
|
|
69
|
+
if (config.default_post_config && config.default_post_config.length > 0) {
|
|
70
|
+
console.log(chalk.cyan('\nDefault Post-Config Tasks:'));
|
|
71
|
+
for (const task of config.default_post_config) {
|
|
72
|
+
const cmd = task.command || task.script || '(unknown)';
|
|
73
|
+
const desc = task.description ? ` ā ${task.description}` : '';
|
|
74
|
+
const checked = task.checked !== false ? '[default: on]' : '[default: off]';
|
|
75
|
+
const typeFilter = task.type ? ` [type: ${task.type}]` : '';
|
|
76
|
+
console.log(chalk.gray(` - ${cmd}${desc}`));
|
|
77
|
+
console.log(chalk.gray(` ${checked}${typeFilter}`));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Show global variables
|
|
82
|
+
if (config.variables && config.variables.length > 0) {
|
|
83
|
+
console.log(chalk.cyan('\nGlobal Variables:'));
|
|
84
|
+
for (const v of config.variables) {
|
|
85
|
+
console.log(chalk.white(` - ${v.name}:`), chalk.gray(v.default || '(no default)'));
|
|
86
|
+
if (v.prompt) console.log(chalk.gray(` Prompt: ${v.prompt}`));
|
|
87
|
+
if (v.required) console.log(chalk.yellow(` [Required]`));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
console.log(chalk.cyan('\nExample post-config in config.yaml:'));
|
|
92
|
+
console.log(chalk.gray(`
|
|
93
|
+
my_template:
|
|
94
|
+
description: "My standard web project"
|
|
95
|
+
post_config:
|
|
96
|
+
- command: "git init"
|
|
97
|
+
description: "Initialize git repository"
|
|
98
|
+
- command: "npm install"
|
|
99
|
+
description: "Install npm dependencies"
|
|
100
|
+
type: "javascript"
|
|
101
|
+
`));
|
|
102
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { loadConfig, saveConfig } from '../config.js';
|
|
2
|
+
|
|
3
|
+
export interface IgnoreOptions {
|
|
4
|
+
set?: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function ignoreCommand(patterns: string | undefined, options: IgnoreOptions = {}) {
|
|
8
|
+
const config = loadConfig();
|
|
9
|
+
|
|
10
|
+
if (options.set) {
|
|
11
|
+
config.ignore = patterns ? patterns.split(',').map((s: string) => s.trim()).filter((s: string) => s !== '') : [];
|
|
12
|
+
saveConfig(config);
|
|
13
|
+
console.log('Ignore patterns updated:', config.ignore);
|
|
14
|
+
} else {
|
|
15
|
+
console.log('Current ignore patterns:', config.ignore || []);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import { loadConfig, FolderNode } from '../config.js';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { processCopyFiles } from '../substitute.js';
|
|
7
|
+
import { execSync } from 'child_process';
|
|
8
|
+
|
|
9
|
+
export interface InitOptions {
|
|
10
|
+
skipPostConfig?: boolean;
|
|
11
|
+
dryRun?: boolean;
|
|
12
|
+
yes?: boolean;
|
|
13
|
+
vars?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function init(targetName: string | undefined, destPath: string | undefined, options: InitOptions = {}) {
|
|
17
|
+
const config = loadConfig();
|
|
18
|
+
|
|
19
|
+
let typeName: string | undefined = targetName;
|
|
20
|
+
|
|
21
|
+
// If no name provided, list templates
|
|
22
|
+
if (!typeName) {
|
|
23
|
+
const names = Object.keys(config.templates);
|
|
24
|
+
if (names.length === 0) {
|
|
25
|
+
console.log(chalk.red("No templates found. Run 'pt learn <path>' first."));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (options.yes) {
|
|
30
|
+
console.error(chalk.red("No project type specified and running in non-interactive mode."));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
const { selected } = await inquirer.prompt({
|
|
34
|
+
type: 'list',
|
|
35
|
+
name: 'selected',
|
|
36
|
+
message: 'Select Project Type:',
|
|
37
|
+
loop: false,
|
|
38
|
+
theme: {
|
|
39
|
+
icon: {
|
|
40
|
+
cursor: chalk.green('[x] ')
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
choices: names.map(n => ({ name: n, value: n }))
|
|
44
|
+
});
|
|
45
|
+
typeName = selected;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const template = config.templates[typeName!];
|
|
49
|
+
if (!template) {
|
|
50
|
+
console.error(chalk.red(`Template "${typeName}" not found.`));
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let dest: string | undefined = destPath;
|
|
55
|
+
if (!dest) {
|
|
56
|
+
if (options.yes) {
|
|
57
|
+
console.error(chalk.red("No destination path specified and running in non-interactive mode."));
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
const { name } = await inquirer.prompt({
|
|
61
|
+
type: 'input',
|
|
62
|
+
name: 'name',
|
|
63
|
+
message: 'Project path/folder name:'
|
|
64
|
+
});
|
|
65
|
+
dest = name;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const resolvedDest = path.resolve(dest!);
|
|
69
|
+
|
|
70
|
+
if (fs.existsSync(resolvedDest) && !options.dryRun) {
|
|
71
|
+
console.error(chalk.red(`Error: Destination "${resolvedDest}" already exists.`));
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (options.dryRun) {
|
|
76
|
+
console.log(chalk.yellow(`\n[DRY RUN] Initializing project "${template.description}" at: ${resolvedDest}`));
|
|
77
|
+
} else {
|
|
78
|
+
console.log(chalk.cyan(`\nInitializing project "${template.description}" at: ${resolvedDest}`));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Handle Variables
|
|
82
|
+
let variables: Record<string, string> = {};
|
|
83
|
+
if (template.variables && template.variables.length > 0) {
|
|
84
|
+
if (options.vars) {
|
|
85
|
+
// Parse --vars "key=val,key2=val2"
|
|
86
|
+
const pairs = options.vars.split(',').map((p: string) => p.trim());
|
|
87
|
+
for (const pair of pairs) {
|
|
88
|
+
const [k, ...v] = pair.split('=');
|
|
89
|
+
if (k && v.length > 0) {
|
|
90
|
+
variables[k.trim()] = v.join('=').trim();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!options.yes) {
|
|
96
|
+
// Prompt for any missing variables
|
|
97
|
+
for (const v of template.variables) {
|
|
98
|
+
if (!variables[v.name]) {
|
|
99
|
+
const answer = await inquirer.prompt({
|
|
100
|
+
type: 'input',
|
|
101
|
+
name: v.name,
|
|
102
|
+
message: v.prompt || `Enter ${v.name}:`,
|
|
103
|
+
default: v.default || ''
|
|
104
|
+
});
|
|
105
|
+
variables[v.name] = answer[v.name];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
// Non-interactive mode: check required
|
|
110
|
+
for (const v of template.variables) {
|
|
111
|
+
if (!variables[v.name]) {
|
|
112
|
+
if (v.required) {
|
|
113
|
+
console.error(chalk.red(`Error: Variable "${v.name}" is required but was not provided in non-interactive mode. Use --vars ${v.name}=value`));
|
|
114
|
+
process.exit(1);
|
|
115
|
+
} else {
|
|
116
|
+
variables[v.name] = v.default || '';
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 1. Create structure
|
|
124
|
+
createStructure(resolvedDest, template.folders, options.dryRun);
|
|
125
|
+
|
|
126
|
+
// Check if templateRoot exists (if it's defined)
|
|
127
|
+
const templateRootExists = template.templateRoot && fs.existsSync(template.templateRoot);
|
|
128
|
+
if (template.templateRoot && !templateRootExists) {
|
|
129
|
+
console.warn(chalk.yellow(`\nWarning: Template source directory not found: ${template.templateRoot}`));
|
|
130
|
+
console.warn(chalk.gray("Folder structure created, but files/boilerplate will be skipped."));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 2. Process copy_files
|
|
134
|
+
if (template.copy_files && templateRootExists) {
|
|
135
|
+
if (options.dryRun) console.log(chalk.yellow("[DRY RUN] Processing copy_files..."));
|
|
136
|
+
else console.log(chalk.cyan("Processing copy_files..."));
|
|
137
|
+
await processCopyFiles(template.templateRoot!, resolvedDest, template, variables, options.dryRun);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
// 3. Process post_copy (executable scripts)
|
|
142
|
+
if (template.post_copy && templateRootExists) {
|
|
143
|
+
if (options.dryRun) console.log(chalk.yellow("[DRY RUN] Processing post_copy..."));
|
|
144
|
+
else console.log(chalk.cyan("Processing post_copy..."));
|
|
145
|
+
|
|
146
|
+
for (const file of template.post_copy) {
|
|
147
|
+
const srcPath = path.join(template.templateRoot!, file.src);
|
|
148
|
+
const destPath = path.join(resolvedDest, file.dest || file.src);
|
|
149
|
+
|
|
150
|
+
if (fs.existsSync(srcPath)) {
|
|
151
|
+
if (options.dryRun) {
|
|
152
|
+
console.log(chalk.gray(` [DRY RUN] Would copy ${file.src} ā ${file.dest || file.src}`));
|
|
153
|
+
const ext = path.extname(file.src);
|
|
154
|
+
if (['.sh', '.py', '.bash', '.bat'].includes(ext)) {
|
|
155
|
+
console.log(chalk.gray(` [DRY RUN] Would chmod +x ${file.dest || file.src}`));
|
|
156
|
+
}
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const fileContent = fs.readFileSync(srcPath, 'utf-8');
|
|
161
|
+
const destDir = path.dirname(destPath);
|
|
162
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
163
|
+
fs.writeFileSync(destPath, fileContent);
|
|
164
|
+
|
|
165
|
+
// Auto-chmod for executables
|
|
166
|
+
const ext = path.extname(file.src);
|
|
167
|
+
if (['.sh', '.py', '.bash', '.bat'].includes(ext)) {
|
|
168
|
+
try {
|
|
169
|
+
fs.chmodSync(destPath, 0o755);
|
|
170
|
+
} catch (e) {
|
|
171
|
+
// chmod not available (Windows)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
console.log(chalk.green(" ā " + (file.dest || file.src)));
|
|
175
|
+
} else {
|
|
176
|
+
console.warn(chalk.yellow(" ! " + file.src + " not found, skipping"));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// Write .info.md
|
|
181
|
+
if (!options.dryRun) {
|
|
182
|
+
const infoContent = `# ${typeName}\n\n${template.description || ''}\n`;
|
|
183
|
+
fs.writeFileSync(path.join(resolvedDest, '.info.md'), infoContent);
|
|
184
|
+
} else {
|
|
185
|
+
console.log(chalk.gray(` [DRY RUN] Would create .info.md`));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Use template post_config tasks
|
|
189
|
+
const allTasks = template.post_config?.filter(t => !t.type || t.type === typeName!) || [];
|
|
190
|
+
|
|
191
|
+
if (allTasks.length > 0) {
|
|
192
|
+
// Determine which tasks to include
|
|
193
|
+
let selectedTaskNames: string[] = [];
|
|
194
|
+
|
|
195
|
+
if (options.skipPostConfig) {
|
|
196
|
+
// Skip entirely
|
|
197
|
+
selectedTaskNames = [];
|
|
198
|
+
} else if (options.dryRun) {
|
|
199
|
+
// In dry-run, select all (for display)
|
|
200
|
+
selectedTaskNames = allTasks.map(t => t.command || t.script || '');
|
|
201
|
+
console.log(chalk.yellow(`\n[DRY RUN] Applicable post-config tasks:`));
|
|
202
|
+
for (const t of allTasks) {
|
|
203
|
+
const desc = t.description ? ` (${t.description})` : '';
|
|
204
|
+
console.log(chalk.gray(` [template] - ${t.command || t.script}${desc}`));
|
|
205
|
+
}
|
|
206
|
+
} else if (options.yes) {
|
|
207
|
+
// All tasks selected
|
|
208
|
+
selectedTaskNames = allTasks.map(t => t.command || t.script || '');
|
|
209
|
+
} else if (allTasks.length === 0) {
|
|
210
|
+
selectedTaskNames = [];
|
|
211
|
+
} else {
|
|
212
|
+
// Checkbox prompt
|
|
213
|
+
const choices: Array<{name: string; value: string; checked?: boolean}> = [];
|
|
214
|
+
|
|
215
|
+
for (const t of allTasks) {
|
|
216
|
+
const cmd = t.command || t.script || '(no command)';
|
|
217
|
+
const desc = t.description ? ` (${t.description})` : '';
|
|
218
|
+
choices.push({
|
|
219
|
+
name: `${cmd}${desc}`,
|
|
220
|
+
value: cmd,
|
|
221
|
+
checked: true
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const response = await inquirer.prompt({
|
|
226
|
+
type: 'checkbox',
|
|
227
|
+
name: 'selected',
|
|
228
|
+
message: 'Select post-config tasks to run:',
|
|
229
|
+
loop: false,
|
|
230
|
+
theme: {
|
|
231
|
+
icon: {
|
|
232
|
+
cursor: chalk.green('[x] ')
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
choices
|
|
236
|
+
});
|
|
237
|
+
selectedTaskNames = response.selected || [];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Write post_config scripts for selected tasks
|
|
241
|
+
if (selectedTaskNames.length > 0 && !options.dryRun) {
|
|
242
|
+
let bashContent = '#!/bin/bash\n# Auto-generated post_config script\n\n';
|
|
243
|
+
let batContent = '@echo off\n:: Auto-generated post_config script\n\n';
|
|
244
|
+
for (const t of allTasks) {
|
|
245
|
+
// Determine the actual command/script to use
|
|
246
|
+
let cmd = '';
|
|
247
|
+
if (t.command) {
|
|
248
|
+
cmd = t.command;
|
|
249
|
+
} else if (t.script) {
|
|
250
|
+
cmd = `./${t.script}`;
|
|
251
|
+
}
|
|
252
|
+
// Match against selected names (use command if available, else script)
|
|
253
|
+
const taskKey = t.command || (t.script ? `./${t.script}` : '');
|
|
254
|
+
if (selectedTaskNames.includes(taskKey)) {
|
|
255
|
+
if (cmd) {
|
|
256
|
+
bashContent += `echo "Running: ${t.description || taskKey}"\n${cmd}\n`;
|
|
257
|
+
batContent += `echo Running: ${t.description || taskKey}\n${cmd}\n`;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
fs.writeFileSync(path.join(resolvedDest, 'post_config.sh'), bashContent);
|
|
262
|
+
try { fs.chmodSync(path.join(resolvedDest, 'post_config.sh'), 0o755); } catch(e) {}
|
|
263
|
+
fs.writeFileSync(path.join(resolvedDest, 'post_config.bat'), batContent);
|
|
264
|
+
|
|
265
|
+
// Execute the appropriate script
|
|
266
|
+
console.log(chalk.cyan("\nExecuting post-config tasks..."));
|
|
267
|
+
try {
|
|
268
|
+
const scriptCmd = process.platform === 'win32' ? 'post_config.bat' : './post_config.sh';
|
|
269
|
+
execSync(scriptCmd, {
|
|
270
|
+
cwd: resolvedDest,
|
|
271
|
+
stdio: 'inherit'
|
|
272
|
+
});
|
|
273
|
+
} catch (e) {
|
|
274
|
+
console.error(chalk.red("\nError: Some post-config tasks failed. Check the output above."));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (options.dryRun) {
|
|
280
|
+
console.log(chalk.yellow(`\n[DRY RUN] Project initialization preview complete.`));
|
|
281
|
+
} else {
|
|
282
|
+
console.log(chalk.green(`\nā Project created successfully.`));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function createStructure(dirPath: string, folders: FolderNode[], dryRun: boolean = false) {
|
|
287
|
+
for (const folder of folders) {
|
|
288
|
+
const fullDirPath = path.join(dirPath, folder.name);
|
|
289
|
+
|
|
290
|
+
if (dryRun) {
|
|
291
|
+
console.log(chalk.gray(` [DRY RUN] Would create directory: ${fullDirPath}`));
|
|
292
|
+
} else {
|
|
293
|
+
fs.mkdirSync(fullDirPath, { recursive: true });
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Create .info.md if content exists
|
|
297
|
+
if (folder.info) {
|
|
298
|
+
const infoPath = path.join(fullDirPath, '.info.md');
|
|
299
|
+
if (dryRun) {
|
|
300
|
+
console.log(chalk.gray(` [DRY RUN] Would create info file: ${infoPath}`));
|
|
301
|
+
} else {
|
|
302
|
+
fs.writeFileSync(infoPath, folder.info);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Recurse children
|
|
307
|
+
if (folder.children && folder.children.length > 0) {
|
|
308
|
+
createStructure(fullDirPath, folder.children, dryRun);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|