da-proj 1.0.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.
@@ -0,0 +1,236 @@
1
+ import { mkdir, writeFile, readFile } from "fs/promises";
2
+ import { existsSync } from "fs";
3
+ import { input, select } from "@inquirer/prompts";
4
+ import type { ProjectMetadata } from "../types/index.js";
5
+ import { log, colors } from "../utils/logger.js";
6
+ import { generateMDX } from "../generators/mdx.js";
7
+ import { generateWorkflow } from "../generators/workflow.js";
8
+ import { generateReadme } from "../generators/readme.js";
9
+ import { generateSchema } from "../generators/schema.js";
10
+
11
+ export async function initCommand() {
12
+ log.title("🚀 Project Metadata Setup");
13
+
14
+ // Verificar que estamos en un repositorio git
15
+ if (!existsSync(".git")) {
16
+ log.error("Not a git repository. Please run this command in a git repository.");
17
+ process.exit(1);
18
+ }
19
+
20
+ // Recopilar información del proyecto
21
+ log.info("Let's set up your project metadata...\n");
22
+
23
+ const title = await input({
24
+ message: "Project title",
25
+ default: "My Awesome Project"
26
+ });
27
+
28
+ const category = await input({
29
+ message: "Category",
30
+ default: "Web Development"
31
+ });
32
+
33
+ const type = await select({
34
+ message: "Project type",
35
+ choices: [
36
+ { value: "small", name: "Small project" },
37
+ { value: "featured", name: "Featured project" }
38
+ ]
39
+ }) as "featured" | "small";
40
+
41
+ const status = await select({
42
+ message: "Project status",
43
+ choices: [
44
+ { value: "in-progress", name: "In Progress" },
45
+ { value: "active", name: "Active" },
46
+ { value: "archived", name: "Archived" }
47
+ ]
48
+ }) as "active" | "archived" | "in-progress";
49
+
50
+ const age = await input({
51
+ message: "Project age (optional)",
52
+ default: "+2 months"
53
+ });
54
+
55
+ const demo = await input({
56
+ message: "Demo URL (optional)",
57
+ default: ""
58
+ });
59
+
60
+ // Tecnologías - input múltiple
61
+ const techInput = await input({
62
+ message: "Technologies (comma separated)",
63
+ default: "React, TypeScript, Node.js"
64
+ });
65
+ const technologies = techInput.split(",").map(t => t.trim()).filter(Boolean);
66
+
67
+ const coverImage = await input({
68
+ message: "Cover image path",
69
+ default: "/proj-images/cover.png"
70
+ });
71
+
72
+ const galleryInput = await input({
73
+ message: "Gallery images (comma separated, optional)",
74
+ default: ""
75
+ });
76
+ const galleryImages = galleryInput ? galleryInput.split(",").map(t => t.trim()).filter(Boolean) : [];
77
+
78
+ let industry, timeline, details;
79
+ if (type === "featured") {
80
+ industry = await input({
81
+ message: "Industry",
82
+ default: "Technology"
83
+ });
84
+
85
+ timeline = await input({
86
+ message: "Timeline",
87
+ default: "Still Working"
88
+ });
89
+
90
+ const detailsInput = await input({
91
+ message: "Project details (comma separated)",
92
+ default: "Built with modern stack, Scalable architecture, Production ready"
93
+ });
94
+ details = detailsInput.split(",").map(t => t.trim()).filter(Boolean);
95
+ }
96
+
97
+ const metadata: ProjectMetadata = {
98
+ title,
99
+ category,
100
+ type,
101
+ status,
102
+ age: age || undefined,
103
+ demo: demo || undefined,
104
+ technologies,
105
+ images: {
106
+ cover: coverImage,
107
+ gallery: galleryImages,
108
+ },
109
+ industry,
110
+ timeline,
111
+ details,
112
+ };
113
+
114
+ // Contenido inicial del MDX
115
+ const mdxContent = `# ${title}
116
+
117
+ ## Description
118
+
119
+ [Add your project description here]
120
+
121
+ ## Key Features
122
+
123
+ - Feature 1
124
+ - Feature 2
125
+ - Feature 3
126
+
127
+ ## Challenges
128
+
129
+ [Describe the technical challenges you faced]
130
+
131
+ ## Outcomes
132
+
133
+ [What did you learn and achieve?]
134
+
135
+ ## Future Improvements
136
+
137
+ - [ ] Improvement 1
138
+ - [ ] Improvement 2
139
+ `;
140
+
141
+ // Crear archivos
142
+ log.info("\nCreating files...");
143
+
144
+ try {
145
+ // .project-metadata.mdx
146
+ await writeFile(".project-metadata.mdx", generateMDX(metadata, mdxContent));
147
+ log.success("Created .project-metadata.mdx");
148
+
149
+ // .github/workflows/sync-portfolio.yml
150
+ await mkdir(".github/workflows", { recursive: true });
151
+ await writeFile(".github/workflows/sync-portfolio.yml", generateWorkflow());
152
+ log.success("Created .github/workflows/sync-portfolio.yml");
153
+
154
+ // README.md (solo si no existe)
155
+ if (!existsSync("README.md")) {
156
+ await writeFile("README.md", generateReadme(metadata));
157
+ log.success("Created README.md");
158
+ } else {
159
+ log.warn("README.md already exists, skipping");
160
+ }
161
+
162
+ // .project-schema.json
163
+ await writeFile(".project-schema.json", generateSchema());
164
+ log.success("Created .project-schema.json");
165
+
166
+ // Crear carpeta de imágenes con README
167
+ await mkdir("proj-images", { recursive: true });
168
+ const imagesReadme = `# Project Images Folder
169
+
170
+ Place your project images here:
171
+
172
+ - **cover.png/jpg**: Main cover image for your project
173
+ - **screenshot1.png/jpg**: Gallery image 1
174
+ - **screenshot2.png/jpg**: Gallery image 2
175
+ - **screenshot3.png/jpg**: Gallery image 3
176
+
177
+ ## Recommended sizes:
178
+
179
+ - Cover image: 1200x630px (or 16:9 ratio)
180
+ - Screenshots: 1920x1080px or similar
181
+
182
+ ## Tips:
183
+
184
+ - Use descriptive filenames
185
+ - Optimize images before uploading (use tools like TinyPNG)
186
+ - Supported formats: PNG, JPG, WebP
187
+ - Keep file sizes under 500KB for better performance
188
+
189
+ ## Current images needed:
190
+
191
+ ${coverImage ? `- [ ] ${coverImage}` : ''}
192
+ ${galleryImages.map(img => `- [ ] ${img}`).join('\n')}
193
+ `;
194
+ await writeFile("proj-images/README.md", imagesReadme);
195
+ await writeFile("proj-images/.gitkeep", "");
196
+ log.success("Created proj-images/ folder");
197
+
198
+ // .gitignore update
199
+ let gitignore = "";
200
+ if (existsSync(".gitignore")) {
201
+ gitignore = await readFile(".gitignore", "utf-8");
202
+ }
203
+
204
+ if (!gitignore.includes("node_modules")) {
205
+ gitignore += "\n# Dependencies\nnode_modules/\n";
206
+ await writeFile(".gitignore", gitignore);
207
+ log.success("Updated .gitignore");
208
+ }
209
+
210
+ log.title("✨ Setup Complete!");
211
+
212
+ console.log(`
213
+ ${colors.bright}Next steps:${colors.reset}
214
+
215
+ 1. ${colors.cyan}Add images${colors.reset} to the proj-images/ folder:
216
+ - Cover image: ${coverImage}
217
+ ${galleryImages.length > 0 ? galleryImages.map(img => `- Gallery: ${img}`).join('\n ') : ''}
218
+
219
+ 2. ${colors.cyan}Review and edit${colors.reset} .project-metadata.mdx
220
+
221
+ 3. ${colors.cyan}Set up secrets${colors.reset} in GitHub:
222
+ Run: ${colors.yellow}da-proj --secrets${colors.reset}
223
+
224
+ 4. ${colors.cyan}Commit and push${colors.reset}:
225
+ ${colors.yellow}git add .
226
+ git commit -m "Add portfolio metadata"
227
+ git push${colors.reset}
228
+
229
+ ${colors.bright}Portfolio will auto-sync on push!${colors.reset} 🎉
230
+ `);
231
+
232
+ } catch (error) {
233
+ log.error("Failed to create files: " + error);
234
+ process.exit(1);
235
+ }
236
+ }
@@ -0,0 +1,119 @@
1
+ import { select } from "@inquirer/prompts";
2
+ import { existsSync } from "fs";
3
+ import { readFile, writeFile } from "fs/promises";
4
+ import type { Config } from "../types/index.js";
5
+ import { getConfigPath } from "../utils/config.js";
6
+ import { readGitHubSyncConfig } from "../utils/github-config.js";
7
+ import { downloadFile } from "../utils/github.js";
8
+ import { colors, log } from "../utils/logger.js";
9
+
10
+ export async function pullCommand() {
11
+ log.title("📥 Pull from GitHub");
12
+
13
+ // 1. Verificar que GitHub sync esté configurado
14
+ const syncConfig = await readGitHubSyncConfig();
15
+ if (!syncConfig) {
16
+ log.error("GitHub sync not configured.");
17
+ console.log(
18
+ `\nRun: ${colors.cyan}da-proj --setup-github-sync${colors.reset}\n`
19
+ );
20
+ process.exit(1);
21
+ }
22
+
23
+ // 2. Descargar desde GitHub
24
+ log.info("Downloading configuration from GitHub...");
25
+
26
+ let remoteConfig: string;
27
+ try {
28
+ remoteConfig = await downloadFile(
29
+ syncConfig.repoUrl,
30
+ "da-proj-config.json"
31
+ );
32
+ } catch (error: any) {
33
+ if (error.message.includes("not found")) {
34
+ log.error("No configuration found in GitHub.");
35
+ console.log(
36
+ `\nUpload your configuration first with: ${colors.cyan}da-proj --push${colors.reset}\n`
37
+ );
38
+ } else {
39
+ log.error(`Failed to download: ${error.message}`);
40
+ }
41
+ process.exit(1);
42
+ }
43
+
44
+ // Validar que sea JSON válido
45
+ let remoteParsed: Config;
46
+ try {
47
+ remoteParsed = JSON.parse(remoteConfig);
48
+ } catch (error) {
49
+ log.error("Downloaded configuration is not valid JSON.");
50
+ process.exit(1);
51
+ }
52
+
53
+ // 3. Verificar si existe config local
54
+ const localConfigPath = getConfigPath();
55
+
56
+ if (existsSync(localConfigPath)) {
57
+ const localConfig = await readFile(localConfigPath, "utf-8");
58
+
59
+ // Comparar
60
+ if (localConfig === remoteConfig) {
61
+ log.success("Already up to date!");
62
+ console.log("\nLocal and GitHub configurations are identical.\n");
63
+ return;
64
+ }
65
+
66
+ // Preguntar qué hacer
67
+ const action = await select({
68
+ message: "Local configuration exists. What do you want to do?",
69
+ choices: [
70
+ { value: "replace", name: "Replace local with GitHub version" },
71
+ { value: "merge", name: "Merge (combine profiles from both)" },
72
+ { value: "cancel", name: "Cancel" },
73
+ ],
74
+ });
75
+
76
+ if (action === "cancel") {
77
+ log.info("Pull cancelled.");
78
+ return;
79
+ }
80
+
81
+ if (action === "merge") {
82
+ // Merge profiles
83
+ const local: Config = JSON.parse(localConfig);
84
+ const remote: Config = remoteParsed;
85
+
86
+ const localProfiles = local.profiles || [];
87
+ const remoteProfiles = remote.profiles || [];
88
+
89
+ // Combinar: primero los de GitHub, luego los locales que no existan en GitHub
90
+ const merged: Config = {
91
+ profiles: [
92
+ ...remoteProfiles,
93
+ ...localProfiles.filter(
94
+ (lp) => !remoteProfiles.some((rp) => rp.name === lp.name)
95
+ ),
96
+ ],
97
+ };
98
+
99
+ remoteConfig = JSON.stringify(merged, null, 2);
100
+ log.info(`Merged: ${merged.profiles?.length || 0} total profiles`);
101
+ }
102
+ }
103
+
104
+ // 4. Guardar localmente
105
+ await writeFile(localConfigPath, remoteConfig);
106
+ log.success("Configuration pulled from GitHub!");
107
+
108
+ // Mostrar perfiles
109
+ const config: Config = JSON.parse(remoteConfig);
110
+ if (config.profiles && config.profiles.length > 0) {
111
+ console.log(
112
+ `\n${colors.bright}Profiles (${config.profiles.length}):${colors.reset}`
113
+ );
114
+ config.profiles.forEach((p) => {
115
+ console.log(` • ${p.name} (${p.portfolioUrl})`);
116
+ });
117
+ console.log("");
118
+ }
119
+ }
@@ -0,0 +1,68 @@
1
+ import { existsSync } from "fs";
2
+ import { readFile } from "fs/promises";
3
+ import { getConfigPath } from "../utils/config.js";
4
+ import { readGitHubSyncConfig } from "../utils/github-config.js";
5
+ import { uploadFile } from "../utils/github.js";
6
+ import { colors, log } from "../utils/logger.js";
7
+
8
+ export async function pushCommand() {
9
+ log.title("📤 Push to GitHub");
10
+
11
+ // 1. Verificar que GitHub sync esté configurado
12
+ const syncConfig = await readGitHubSyncConfig();
13
+ if (!syncConfig) {
14
+ log.error("GitHub sync not configured.");
15
+ console.log(
16
+ `\nRun: ${colors.cyan}da-proj --setup-github-sync${colors.reset}\n`
17
+ );
18
+ process.exit(1);
19
+ }
20
+
21
+ // 2. Leer configuración local
22
+ const localConfigPath = getConfigPath();
23
+ if (!existsSync(localConfigPath)) {
24
+ log.error("No local configuration found.");
25
+ console.log(
26
+ `\nCreate profiles first with: ${colors.cyan}da-proj --secrets${colors.reset}\n`
27
+ );
28
+ process.exit(1);
29
+ }
30
+
31
+ const localConfig = await readFile(localConfigPath, "utf-8");
32
+
33
+ // Validar que sea JSON válido
34
+ try {
35
+ JSON.parse(localConfig);
36
+ } catch (error) {
37
+ log.error("Local configuration is not valid JSON.");
38
+ process.exit(1);
39
+ }
40
+
41
+ // 3. Subir a GitHub
42
+ log.info("Uploading configuration to GitHub...");
43
+
44
+ try {
45
+ await uploadFile(syncConfig.repoUrl, "da-proj-config.json", localConfig);
46
+
47
+ log.success("Configuration pushed to GitHub!");
48
+ console.log(
49
+ `\n${colors.bright}Repository:${colors.reset} ${syncConfig.repoUrl}`
50
+ );
51
+ console.log(`${colors.bright}File:${colors.reset} da-proj-config.json\n`);
52
+
53
+ // Mostrar resumen
54
+ const config = JSON.parse(localConfig);
55
+ if (config.profiles && config.profiles.length > 0) {
56
+ console.log(
57
+ `${colors.bright}Uploaded ${config.profiles.length} profile(s):${colors.reset}`
58
+ );
59
+ config.profiles.forEach((p: any) => {
60
+ console.log(` • ${p.name} (${p.portfolioUrl})`);
61
+ });
62
+ console.log("");
63
+ }
64
+ } catch (error: any) {
65
+ log.error(`Failed to push: ${error.message}`);
66
+ process.exit(1);
67
+ }
68
+ }
@@ -0,0 +1,251 @@
1
+ import { input, select } from "@inquirer/prompts";
2
+ import { log, colors } from "../utils/logger.js";
3
+ import {
4
+ checkGitHubCLI,
5
+ getExistingSecrets,
6
+ setGitHubSecret
7
+ } from "../utils/github.js";
8
+ import {
9
+ readGlobalConfig,
10
+ saveGlobalConfig,
11
+ getConfigPath
12
+ } from "../utils/config.js";
13
+ import { exec } from "child_process";
14
+ import { promisify } from "util";
15
+
16
+ const execAsync = promisify(exec);
17
+
18
+ export async function secretsCommand() {
19
+ log.title("🔐 GitHub Secrets Setup");
20
+
21
+ // Verificar GitHub CLI
22
+ const hasGH = await checkGitHubCLI();
23
+ if (!hasGH) {
24
+ log.error("GitHub CLI (gh) is not installed.");
25
+ console.log(`
26
+ ${colors.yellow}To install GitHub CLI:${colors.reset}
27
+
28
+ ${colors.bright}Windows:${colors.reset}
29
+ winget install --id GitHub.cli
30
+
31
+ ${colors.bright}Or download from:${colors.reset}
32
+ https://cli.github.com/
33
+
34
+ ${colors.bright}After installing, authenticate:${colors.reset}
35
+ gh auth login
36
+ `);
37
+ process.exit(1);
38
+ }
39
+
40
+ // Verificar autenticación
41
+ try {
42
+ await execAsync('gh auth status');
43
+ } catch {
44
+ log.error("Not authenticated with GitHub CLI.");
45
+ console.log(`
46
+ ${colors.yellow}Please authenticate first:${colors.reset}
47
+ gh auth login
48
+ `);
49
+ process.exit(1);
50
+ }
51
+
52
+ log.info("GitHub CLI is ready!\n");
53
+
54
+ // Verificar si ya existen secrets en este repo
55
+ const existingSecrets = await getExistingSecrets();
56
+
57
+ if (existingSecrets.url || existingSecrets.key) {
58
+ log.warn("Found existing secrets in this repository:");
59
+ if (existingSecrets.url) console.log(` ${colors.yellow}✓${colors.reset} PORTFOLIO_API_URL`);
60
+ if (existingSecrets.key) console.log(` ${colors.yellow}✓${colors.reset} PORTFOLIO_API_KEY`);
61
+ console.log('');
62
+
63
+ const overwrite = await select({
64
+ message: "Do you want to overwrite them?",
65
+ choices: [
66
+ { value: "yes", name: "Yes, overwrite with new configuration" },
67
+ { value: "no", name: "No, keep existing secrets" }
68
+ ]
69
+ });
70
+
71
+ if (overwrite === "no") {
72
+ log.info("Keeping existing secrets. No changes made.");
73
+ return;
74
+ }
75
+
76
+ console.log('');
77
+ }
78
+
79
+ // Leer configuración existente
80
+ const config = await readGlobalConfig();
81
+ const profiles = config.profiles || [];
82
+
83
+ let selectedProfile: { name: string; portfolioUrl: string; apiKey: string } | null = null;
84
+
85
+ if (profiles.length > 0) {
86
+ log.info(`Found ${profiles.length} saved profile(s):\n`);
87
+
88
+ const choices = [
89
+ ...profiles.map((p, i) => ({
90
+ value: `profile-${i}`,
91
+ name: `${p.name} (${p.portfolioUrl})`
92
+ })),
93
+ { value: "new", name: "➕ Create new profile" },
94
+ { value: "list", name: "📋 List all profiles" }
95
+ ];
96
+
97
+ const selection = await select({
98
+ message: "Select a profile or create new:",
99
+ choices
100
+ });
101
+
102
+ if (selection === "list") {
103
+ // Mostrar todas las configuraciones
104
+ log.title("📋 Saved Profiles");
105
+ profiles.forEach((p, i) => {
106
+ console.log(`\n${colors.bright}${i + 1}. ${p.name}${colors.reset}`);
107
+ console.log(` URL: ${p.portfolioUrl}`);
108
+ console.log(` Key: ${p.apiKey.substring(0, 16)}...${p.apiKey.substring(p.apiKey.length - 4)}`);
109
+ });
110
+ console.log('');
111
+
112
+ const profileIndex = await input({
113
+ message: "Enter profile number to use (or 0 to create new):",
114
+ default: "1"
115
+ });
116
+
117
+ const idx = parseInt(profileIndex) - 1;
118
+ if (idx >= 0 && idx < profiles.length) {
119
+ selectedProfile = profiles[idx] || null;
120
+ }
121
+ } else if (selection.startsWith("profile-")) {
122
+ const idx = parseInt(selection.replace("profile-", ""));
123
+ selectedProfile = profiles[idx] || null;
124
+ }
125
+
126
+ if (selectedProfile) {
127
+ log.info(`Using profile: ${selectedProfile.name}`);
128
+
129
+ // Usar configuración existente
130
+ try {
131
+ if (existingSecrets.url) {
132
+ log.info("Overwriting PORTFOLIO_API_URL...");
133
+ }
134
+ await setGitHubSecret('PORTFOLIO_API_URL', selectedProfile.portfolioUrl);
135
+ log.success(existingSecrets.url ? "Updated PORTFOLIO_API_URL" : "Set PORTFOLIO_API_URL");
136
+
137
+ if (existingSecrets.key) {
138
+ log.info("Overwriting PORTFOLIO_API_KEY...");
139
+ }
140
+ await setGitHubSecret('PORTFOLIO_API_KEY', selectedProfile.apiKey);
141
+ log.success(existingSecrets.key ? "Updated PORTFOLIO_API_KEY" : "Set PORTFOLIO_API_KEY");
142
+
143
+ log.title("✨ Secrets configured successfully!");
144
+ console.log(`\n${colors.bright}Using profile: ${selectedProfile.name}${colors.reset} 🎉\n`);
145
+ return;
146
+ } catch (error: any) {
147
+ log.error("Failed to set secrets: " + error.message);
148
+ process.exit(1);
149
+ }
150
+ }
151
+ }
152
+
153
+ // Crear nuevo perfil
154
+ const profileName = await input({
155
+ message: "Profile name (e.g., 'main', 'personal', 'work')",
156
+ default: profiles.length === 0 ? "main" : "profile-" + (profiles.length + 1)
157
+ });
158
+
159
+ const portfolioUrl = await input({
160
+ message: "Portfolio API URL",
161
+ default: "https://your-portfolio.com"
162
+ });
163
+
164
+ const portfolioKey = await input({
165
+ message: "Portfolio API Key (leave empty to generate one)",
166
+ default: ""
167
+ });
168
+
169
+ let apiKey = portfolioKey;
170
+
171
+ // Generar API key si está vacía
172
+ if (!apiKey) {
173
+ log.info("Generating API key...");
174
+ const randomBytes = new Uint8Array(32);
175
+ crypto.getRandomValues(randomBytes);
176
+ apiKey = Array.from(randomBytes)
177
+ .map(b => b.toString(16).padStart(2, '0'))
178
+ .join('');
179
+
180
+ console.log(`\n${colors.bright}Generated API Key:${colors.reset}`);
181
+ console.log('━'.repeat(70));
182
+ console.log(apiKey);
183
+ console.log('━'.repeat(70));
184
+ console.log(`\n${colors.yellow}⚠️ IMPORTANT: Save this key!${colors.reset}`);
185
+ console.log(`\n${colors.bright}1. Add it to your portfolio as environment variable:${colors.reset}`);
186
+ console.log(` PORTFOLIO_API_KEY=${apiKey}`);
187
+ console.log(`\n${colors.bright}2. This key will be saved locally and reused for all your projects${colors.reset}\n`);
188
+
189
+ await input({
190
+ message: "Press Enter to continue and set the secrets in GitHub...",
191
+ default: ""
192
+ });
193
+ }
194
+
195
+ // Guardar configuración globalmente
196
+ await saveGlobalConfig({ name: profileName, portfolioUrl, apiKey });
197
+
198
+ // Configurar secrets en GitHub
199
+ log.info("Setting GitHub secrets...");
200
+
201
+ try {
202
+ if (existingSecrets.url) {
203
+ log.info("Overwriting PORTFOLIO_API_URL...");
204
+ }
205
+ await setGitHubSecret('PORTFOLIO_API_URL', portfolioUrl);
206
+ log.success(existingSecrets.url ? "Updated PORTFOLIO_API_URL" : "Set PORTFOLIO_API_URL");
207
+
208
+ if (existingSecrets.key) {
209
+ log.info("Overwriting PORTFOLIO_API_KEY...");
210
+ }
211
+ await setGitHubSecret('PORTFOLIO_API_KEY', apiKey);
212
+ log.success(existingSecrets.key ? "Updated PORTFOLIO_API_KEY" : "Set PORTFOLIO_API_KEY");
213
+
214
+ log.title("✨ Secrets configured successfully!");
215
+
216
+ console.log(`
217
+ ${colors.bright}Configuration saved!${colors.reset}
218
+
219
+ ${colors.green}✓${colors.reset} Profile "${profileName}" saved in: ${getConfigPath()}
220
+ ${colors.green}✓${colors.reset} You can now use this profile in other repos!
221
+
222
+ ${colors.bright}Next steps:${colors.reset}
223
+
224
+ 1. ${colors.cyan}Make sure your portfolio has the API key${colors.reset}:
225
+ PORTFOLIO_API_KEY=${apiKey}
226
+
227
+ 2. ${colors.cyan}Test the workflow${colors.reset}:
228
+ git add .
229
+ git commit -m "Test workflow"
230
+ git push
231
+
232
+ ${colors.bright}All your projects using this profile will sync to the same portfolio!${colors.reset} 🎉
233
+ `);
234
+
235
+ } catch (error: any) {
236
+ log.error("Failed to set secrets: " + error.message);
237
+ console.log(`
238
+ ${colors.yellow}You can set them manually:${colors.reset}
239
+
240
+ 1. Go to your repo on GitHub
241
+ 2. Settings > Secrets and variables > Actions
242
+ 3. New repository secret:
243
+ - Name: PORTFOLIO_API_URL
244
+ - Value: ${portfolioUrl}
245
+ 4. New repository secret:
246
+ - Name: PORTFOLIO_API_KEY
247
+ - Value: ${apiKey}
248
+ `);
249
+ process.exit(1);
250
+ }
251
+ }