kcreate-scaffolding-gcp-script 0.0.0-stage → 0.2.1
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/LICENSE +21 -0
- package/README.md +429 -2
- package/bin/build.js +70 -0
- package/bin/cli.js +441 -0
- package/package.json +36 -4
- package/skill/SKILL.md +262 -0
- package/skill/references/lessons-learned.md +706 -0
- package/skill/scripts/01_init_config.sh +151 -0
- package/skill/scripts/02_print_config.sh +60 -0
- package/skill/scripts/10_build_components.sh +596 -0
- package/skill/scripts/20_validate_file_parity.sh +107 -0
- package/skill/scripts/21_validate_project_pinning.sh +105 -0
- package/skill/scripts/22_validate_no_app_name_leftover.sh +62 -0
- package/skill/scripts/lib/common.sh +788 -0
- package/skill/scripts/run_tracking.sh +93 -0
- package/skill/templates/scaffolding.config.template +81 -0
- package/vendor/github-proyect-tracking/SKILL.md +180 -0
- package/vendor/github-proyect-tracking/references/api.md +185 -0
- package/vendor/github-proyect-tracking/references/architecture.md +171 -0
- package/vendor/github-proyect-tracking/scripts/tracking.py +214 -0
- package/vendor/github-proyect-tracking/scripts/tracking_core/__init__.py +1 -0
- package/vendor/github-proyect-tracking/scripts/tracking_core/commands.py +940 -0
- package/vendor/github-proyect-tracking/scripts/tracking_core/common.py +208 -0
- package/vendor/github-proyect-tracking/scripts/tracking_core/events.py +233 -0
- package/vendor/github-proyect-tracking/scripts/tracking_core/github.py +324 -0
- package/vendor/github-proyect-tracking/scripts/tracking_core/state.py +379 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* kcreate-scaffolding-gcp-script — instalador tipo npx del skill.
|
|
4
|
+
*
|
|
5
|
+
* npx --yes kcreate-scaffolding-gcp-script --target-repo /ruta/al/repo
|
|
6
|
+
* npx --yes kcreate-scaffolding-gcp-script uninstall --target-repo /ruta/al/repo
|
|
7
|
+
* npx --yes kcreate-scaffolding-gcp-script doctor
|
|
8
|
+
*
|
|
9
|
+
* Comandos: install (por defecto) · uninstall · doctor
|
|
10
|
+
* Flags: --target-repo RUTA · --skip-tracking · --force
|
|
11
|
+
* --with-tracking · --clean-gitignore · -h/--help · -v/--version
|
|
12
|
+
*
|
|
13
|
+
* Sin dependencias npm (Node >= 18). Replica exactamente el contrato de
|
|
14
|
+
* install.sh/uninstall.sh de este repo:
|
|
15
|
+
* - destino: <target-repo>/.claude/skills/<skill>/
|
|
16
|
+
* - copia atómica (stage + swap), nunca deja el destino a medias
|
|
17
|
+
* - exit 0 solo si el skill quedó instalado/actualizado (o desinstalado)
|
|
18
|
+
* - exit 1 si no se instaló nada (ya existía sin --force), destino inválido
|
|
19
|
+
* - exit 2 si los argumentos son inválidos (igual que el CLI hermano)
|
|
20
|
+
*/
|
|
21
|
+
'use strict';
|
|
22
|
+
|
|
23
|
+
const { spawnSync } = require('node:child_process');
|
|
24
|
+
const fs = require('node:fs');
|
|
25
|
+
const os = require('node:os');
|
|
26
|
+
const path = require('node:path');
|
|
27
|
+
|
|
28
|
+
const SKILL_NAME = 'kcreate-scaffolding-gcp-script';
|
|
29
|
+
const TRACKING_SKILL_NAME = 'github-proyect-tracking';
|
|
30
|
+
const VERSION = require('../package.json').version;
|
|
31
|
+
const SKILL_SRC = path.join(__dirname, '..', 'skill'); // copia empaquetada
|
|
32
|
+
// Dependencia vendorizada, empaquetada por bin/build.js en prepack (viene de
|
|
33
|
+
// vendor/github-proyect-tracking/ en la raíz del repo).
|
|
34
|
+
const TRACKING_SRC = path.join(__dirname, '..', 'vendor', TRACKING_SKILL_NAME);
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// utilidades
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
const log = (m = '') => console.log(m);
|
|
41
|
+
const ok = (m) => log(`\x1b[32m[OK]\x1b[0m ${m}`);
|
|
42
|
+
const info = (m) => log(`[INFO] ${m}`);
|
|
43
|
+
const warn = (m) => console.log(`\x1b[33m[WARN]\x1b[0m ${m}`);
|
|
44
|
+
const err = (m) => console.error(`\x1b[31m[ERROR]\x1b[0m ${m}`);
|
|
45
|
+
const die = (msg, code = 1) => { err(msg); process.exit(code); };
|
|
46
|
+
|
|
47
|
+
const COMMANDS = new Set(['install', 'uninstall', 'doctor']);
|
|
48
|
+
// Flags que exigen un valor no vacío.
|
|
49
|
+
const VALUE_FLAGS = new Set(['--target-repo']);
|
|
50
|
+
// Flags booleanos por comando.
|
|
51
|
+
const BOOL_FLAGS = {
|
|
52
|
+
install: new Set(['--skip-tracking', '--force']),
|
|
53
|
+
uninstall: new Set(['--with-tracking', '--clean-gitignore']),
|
|
54
|
+
doctor: new Set([]),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function parseArgs(argv) {
|
|
58
|
+
const out = { command: null, flags: {}, rest: [] };
|
|
59
|
+
for (let i = 0; i < argv.length; i++) {
|
|
60
|
+
const a = argv[i];
|
|
61
|
+
if (a === '-h' || a === '--help') out.flags.help = true;
|
|
62
|
+
else if (a === '-v' || a === '--version') out.flags.version = true;
|
|
63
|
+
else if (VALUE_FLAGS.has(a)) {
|
|
64
|
+
const v = argv[i + 1];
|
|
65
|
+
if (v === undefined || !v.trim() || v.startsWith('-')) {
|
|
66
|
+
die(`el flag ${a} requiere un valor no vacío (ej. ${a} /ruta/al/repo)`, 2);
|
|
67
|
+
}
|
|
68
|
+
out.flags.targetRepo = v;
|
|
69
|
+
i++;
|
|
70
|
+
} else if (a.startsWith('-')) {
|
|
71
|
+
out.rest.push(a);
|
|
72
|
+
} else if (out.command === null) {
|
|
73
|
+
out.command = a;
|
|
74
|
+
} else {
|
|
75
|
+
out.rest.push(a);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (out.command === null) out.command = 'install';
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// validateFlags <command> <flags> -> DETENTE con exit 2 si un flag no
|
|
83
|
+
// corresponde al comando (mismo criterio que el CLI hermano, que rechaza
|
|
84
|
+
// --purge-state fuera de uninstall).
|
|
85
|
+
function validateFlags(command, flags) {
|
|
86
|
+
const allowed = BOOL_FLAGS[command] || new Set();
|
|
87
|
+
const known = new Set([...Object.values(BOOL_FLAGS).flatMap((s) => [...s])]);
|
|
88
|
+
for (const a of flags.rest) {
|
|
89
|
+
if (!known.has(a)) die(`flag desconocido: ${a} (usa --help)`, 2);
|
|
90
|
+
if (!allowed.has(a)) die(`${a} no es válido con '${command}' (usa --help)`, 2);
|
|
91
|
+
const key = a.replace(/^--/, '').replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
92
|
+
flags[key] = true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function requireGitRepo(p) {
|
|
97
|
+
if (!fs.existsSync(p) || !fs.statSync(p).isDirectory()) {
|
|
98
|
+
die(`no existe el directorio: ${p}`, 1);
|
|
99
|
+
}
|
|
100
|
+
if (!fs.existsSync(path.join(p, '.git'))) {
|
|
101
|
+
die(`${p} no es la raíz de un repo git (no tiene .git/). Verifica la ruta.`, 1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function resolveTargetRepo(flags) {
|
|
106
|
+
if (!flags.targetRepo) {
|
|
107
|
+
die('falta --target-repo /ruta/al/repo (raíz del repo donde instalar el skill)', 2);
|
|
108
|
+
}
|
|
109
|
+
const p = path.resolve(flags.targetRepo);
|
|
110
|
+
requireGitRepo(p);
|
|
111
|
+
return p;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// cleanCaches <dir> — mismo criterio que install.sh (ksc_copy_skill): nunca
|
|
115
|
+
// copiar __pycache__ ni bytecode al destino.
|
|
116
|
+
function cleanCaches(dir) {
|
|
117
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
118
|
+
const p = path.join(dir, e.name);
|
|
119
|
+
if (e.isDirectory()) {
|
|
120
|
+
if (e.name === '__pycache__') fs.rmSync(p, { recursive: true, force: true });
|
|
121
|
+
else cleanCaches(p);
|
|
122
|
+
} else if (/\.py[co]$/.test(e.name)) {
|
|
123
|
+
fs.rmSync(p, { force: true });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function validateFrontmatter(skillDir, expectedName) {
|
|
129
|
+
const mdPath = path.join(skillDir, 'SKILL.md');
|
|
130
|
+
if (!fs.existsSync(mdPath)) throw new Error(`falta ${mdPath}`);
|
|
131
|
+
const md = fs.readFileSync(mdPath, 'utf8');
|
|
132
|
+
if (!md.startsWith('---')) throw new Error(`${mdPath}: falta "---" inicial`);
|
|
133
|
+
const rel = md.slice(3).search(/\n---\s*\n/);
|
|
134
|
+
if (rel < 0) throw new Error(`${mdPath}: falta cierre "---"`);
|
|
135
|
+
const fm = md.slice(3, 3 + rel);
|
|
136
|
+
const name = (fm.match(/^name:\s*(.+?)\s*$/m) || [])[1];
|
|
137
|
+
if (name !== expectedName) {
|
|
138
|
+
throw new Error(`${mdPath}: name=${name} (esperado ${expectedName})`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// verifySkill <skillDir> — verificación OBLIGATORIA (no un aviso): si el
|
|
143
|
+
// skill está empaquetado a medias, es mejor fallar aquí que instalarlo.
|
|
144
|
+
function verifySkill(skillDir) {
|
|
145
|
+
validateFrontmatter(skillDir, SKILL_NAME);
|
|
146
|
+
const required = [
|
|
147
|
+
'scripts/01_init_config.sh',
|
|
148
|
+
'scripts/02_print_config.sh',
|
|
149
|
+
'scripts/10_build_components.sh',
|
|
150
|
+
'scripts/20_validate_file_parity.sh',
|
|
151
|
+
'scripts/21_validate_project_pinning.sh',
|
|
152
|
+
'scripts/run_tracking.sh',
|
|
153
|
+
'scripts/lib/common.sh',
|
|
154
|
+
'templates/scaffolding.config.template',
|
|
155
|
+
];
|
|
156
|
+
const missing = required.filter((f) => !fs.existsSync(path.join(skillDir, f)));
|
|
157
|
+
if (missing.length) {
|
|
158
|
+
throw new Error(`paquete corrupto, faltan: ${missing.join(', ')}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// verifyTracking <skillDir> — la dependencia es un requisito del skill, pero
|
|
163
|
+
// install.sh permite --skip-tracking: se valida solo si se va a instalar.
|
|
164
|
+
function verifyTracking(skillDir) {
|
|
165
|
+
validateFrontmatter(skillDir, TRACKING_SKILL_NAME);
|
|
166
|
+
if (!fs.existsSync(path.join(skillDir, 'scripts', 'tracking.py'))) {
|
|
167
|
+
throw new Error(`paquete corrupto: falta ${skillDir}/scripts/tracking.py`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// copyModes <src> <dest> — replica los permisos de los hijos del origen, como
|
|
172
|
+
// hace `cp -R` (que install.sh usa): node copia con 644/755 por defecto y eso
|
|
173
|
+
// dejaría los scripts sin los bits que tienen en el repo. El directorio raíz
|
|
174
|
+
// del stage NO se toca: install.sh lo hereda de `mktemp -d` (0700) y aquí se
|
|
175
|
+
// replica igual para que el resultado sea idéntico con los dos instaladores.
|
|
176
|
+
function copyModes(src, dest) {
|
|
177
|
+
let st;
|
|
178
|
+
try {
|
|
179
|
+
st = fs.lstatSync(src);
|
|
180
|
+
} catch { return; }
|
|
181
|
+
try { fs.chmodSync(dest, st.mode & 0o7777); } catch { /* noop */ }
|
|
182
|
+
if (!st.isDirectory()) return;
|
|
183
|
+
for (const name of fs.readdirSync(src)) {
|
|
184
|
+
copyModes(path.join(src, name), path.join(dest, name));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// copyAtomic <src> <dest> <label> — stage + swap, como ksc_copy_skill.
|
|
189
|
+
function copyAtomic(src, dest, label) {
|
|
190
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
191
|
+
const stage = fs.mkdtempSync(path.join(path.dirname(dest), '.ksc-install.'));
|
|
192
|
+
try {
|
|
193
|
+
fs.cpSync(src, stage, { recursive: true });
|
|
194
|
+
cleanCaches(stage);
|
|
195
|
+
// Permisos: los hijos según el origen; el stage conserva el 0700 de
|
|
196
|
+
// mkdtemp (igual que install.sh) y ese modo pasa a ser el del destino.
|
|
197
|
+
for (const name of fs.readdirSync(src)) {
|
|
198
|
+
copyModes(path.join(src, name), path.join(stage, name));
|
|
199
|
+
}
|
|
200
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
201
|
+
fs.renameSync(stage, dest);
|
|
202
|
+
ok(`${label} instalado en ${dest}`);
|
|
203
|
+
} catch (e) {
|
|
204
|
+
fs.rmSync(stage, { recursive: true, force: true });
|
|
205
|
+
throw e;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// addExecBits <file> — replica exactamente `chmod +x` de install.sh (suma
|
|
210
|
+
// los bits de ejecución a los que ya tenía, sin normalizar a 755), para que
|
|
211
|
+
// los permisos del destino sean idénticos con los dos instaladores.
|
|
212
|
+
function addExecBits(file) {
|
|
213
|
+
try {
|
|
214
|
+
const mode = fs.statSync(file).mode & 0o7777;
|
|
215
|
+
fs.chmodSync(file, mode | 0o111);
|
|
216
|
+
} catch { /* noop */ }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function chmodScripts(skillDir) {
|
|
220
|
+
const dirs = [path.join(skillDir, 'scripts'), path.join(skillDir, 'scripts', 'lib')];
|
|
221
|
+
for (const d of dirs) {
|
|
222
|
+
if (!fs.existsSync(d)) continue;
|
|
223
|
+
for (const f of fs.readdirSync(d)) {
|
|
224
|
+
if (f.endsWith('.sh')) addExecBits(path.join(d, f));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
230
|
+
// comandos
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
function install(flags) {
|
|
234
|
+
const target = resolveTargetRepo(flags);
|
|
235
|
+
if (!fs.existsSync(path.join(target, 'live'))) {
|
|
236
|
+
warn(`${target} no tiene un directorio live/ — este skill asume la convención ` +
|
|
237
|
+
'live/<entorno>/<componente>/terragrunt.hcl. Puede fallar.');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const skillsDir = path.join(target, '.claude', 'skills');
|
|
241
|
+
const destMain = path.join(skillsDir, SKILL_NAME);
|
|
242
|
+
const destTracking = path.join(skillsDir, TRACKING_SKILL_NAME);
|
|
243
|
+
|
|
244
|
+
// El skill y su dependencia se validan ANTES de tocar el destino: si el
|
|
245
|
+
// paquete está roto, no se deja una instalación parcial.
|
|
246
|
+
try {
|
|
247
|
+
verifySkill(SKILL_SRC);
|
|
248
|
+
} catch (e) {
|
|
249
|
+
die(e.message, 2);
|
|
250
|
+
}
|
|
251
|
+
if (!flags.skipTracking) {
|
|
252
|
+
try {
|
|
253
|
+
verifyTracking(TRACKING_SRC);
|
|
254
|
+
} catch (e) {
|
|
255
|
+
die(`no se puede instalar ${TRACKING_SKILL_NAME}: ${e.message} ` +
|
|
256
|
+
'(usa --skip-tracking solo si el repo destino ya lo gestiona por su cuenta)', 2);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
let mainInstalled = false;
|
|
261
|
+
if (fs.existsSync(destMain) && !flags.force) {
|
|
262
|
+
err(`${SKILL_NAME} ya existe en ${destMain} — usa --force para sobreescribir.`);
|
|
263
|
+
} else {
|
|
264
|
+
copyAtomic(SKILL_SRC, destMain, SKILL_NAME);
|
|
265
|
+
chmodScripts(destMain);
|
|
266
|
+
mainInstalled = true;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
let trackingInstalled = false;
|
|
270
|
+
if (flags.skipTracking) {
|
|
271
|
+
info(`--skip-tracking: no se tocó ${TRACKING_SKILL_NAME}`);
|
|
272
|
+
} else if (fs.existsSync(destTracking) && !flags.force) {
|
|
273
|
+
info(`${TRACKING_SKILL_NAME} ya existe en el destino — no se sobreescribe (usa --force).`);
|
|
274
|
+
} else {
|
|
275
|
+
copyAtomic(TRACKING_SRC, destTracking, TRACKING_SKILL_NAME);
|
|
276
|
+
addExecBits(path.join(destTracking, 'scripts', 'tracking.py'));
|
|
277
|
+
trackingInstalled = true;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
log();
|
|
281
|
+
// Mismo contrato que install.sh: exit 0 ⇔ el skill quedó instalado.
|
|
282
|
+
if (!mainInstalled) {
|
|
283
|
+
die('No se instaló nada. Vuelve a correr con --force para sobreescribir ' +
|
|
284
|
+
'la instalación existente.', 1);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
ok(`Instalación completa en ${target}`);
|
|
288
|
+
if (!trackingInstalled) info(`${TRACKING_SKILL_NAME} no fue (re)instalado en esta corrida.`);
|
|
289
|
+
log('Siguiente paso: desde una sesión de Claude Code abierta en ese repo,');
|
|
290
|
+
log('invoca el skill con:');
|
|
291
|
+
log();
|
|
292
|
+
log(` /${SKILL_NAME} <SOURCE_ENV> <NEW_ENV>`);
|
|
293
|
+
log();
|
|
294
|
+
log(`Ejemplo: /${SKILL_NAME} boletera-qa xoloshop-prod`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function uninstall(flags) {
|
|
298
|
+
const target = resolveTargetRepo(flags);
|
|
299
|
+
const skillsDir = path.join(target, '.claude', 'skills');
|
|
300
|
+
const destMain = path.join(skillsDir, SKILL_NAME);
|
|
301
|
+
const destTracking = path.join(skillsDir, TRACKING_SKILL_NAME);
|
|
302
|
+
|
|
303
|
+
if (fs.existsSync(destMain)) {
|
|
304
|
+
fs.rmSync(destMain, { recursive: true, force: true });
|
|
305
|
+
ok(`eliminado ${destMain}`);
|
|
306
|
+
} else {
|
|
307
|
+
info(`${SKILL_NAME} no estaba instalado en ${target}`);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (flags.withTracking) {
|
|
311
|
+
if (fs.existsSync(destTracking)) {
|
|
312
|
+
fs.rmSync(destTracking, { recursive: true, force: true });
|
|
313
|
+
ok(`eliminado ${destTracking}`);
|
|
314
|
+
} else {
|
|
315
|
+
info(`${TRACKING_SKILL_NAME} no estaba instalado en ${target}`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (flags.cleanGitignore) {
|
|
320
|
+
const gitignore = path.join(target, '.gitignore');
|
|
321
|
+
const markers = [
|
|
322
|
+
'.kcreate-scaffolding/',
|
|
323
|
+
'# kcreate-scaffolding-gcp-script: configuracion de ejecucion, no comitear',
|
|
324
|
+
];
|
|
325
|
+
if (fs.existsSync(gitignore)) {
|
|
326
|
+
const text = fs.readFileSync(gitignore, 'utf8');
|
|
327
|
+
const endsWithNewline = text.endsWith('\n');
|
|
328
|
+
const lines = endsWithNewline ? text.slice(0, -1).split('\n') : text.split('\n');
|
|
329
|
+
const kept = [];
|
|
330
|
+
for (const line of lines) {
|
|
331
|
+
if (markers.includes(line)) continue;
|
|
332
|
+
// colapsar líneas en blanco consecutivas, igual que `cat -s` en
|
|
333
|
+
// uninstall.sh (que es la implementación de referencia).
|
|
334
|
+
if (line === '' && kept.length && kept[kept.length - 1] === '') continue;
|
|
335
|
+
kept.push(line);
|
|
336
|
+
}
|
|
337
|
+
fs.writeFileSync(
|
|
338
|
+
gitignore,
|
|
339
|
+
kept.join('\n') + (endsWithNewline && kept.length ? '\n' : '')
|
|
340
|
+
);
|
|
341
|
+
ok(`quitada la línea .kcreate-scaffolding/ de ${gitignore}`);
|
|
342
|
+
}
|
|
343
|
+
const stateDir = path.join(target, '.kcreate-scaffolding');
|
|
344
|
+
if (fs.existsSync(stateDir)) {
|
|
345
|
+
fs.rmSync(stateDir, { recursive: true, force: true });
|
|
346
|
+
ok(`eliminado ${stateDir}/`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
log('Listo.');
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// doctor — diagnóstico SIN tocar nada: qué hay instalado en el repo destino y
|
|
353
|
+
// si sus dependencias (bash/python3/terragrunt/git) están disponibles.
|
|
354
|
+
function doctor(flags) {
|
|
355
|
+
const target = resolveTargetRepo(flags);
|
|
356
|
+
const destMain = path.join(target, '.claude', 'skills', SKILL_NAME);
|
|
357
|
+
const destTracking = path.join(target, '.claude', 'skills', TRACKING_SKILL_NAME);
|
|
358
|
+
|
|
359
|
+
log(`kcreate-scaffolding-gcp-script/${VERSION} (instalador npx)`);
|
|
360
|
+
log(`repo destino : ${target}`);
|
|
361
|
+
log(`live/ : ${fs.existsSync(path.join(target, 'live')) ? 'sí' : 'NO (el skill asumirá live/<env>/...)'}`);
|
|
362
|
+
log(`skill : ${fs.existsSync(destMain) ? destMain : 'no instalado'}`);
|
|
363
|
+
log(`tracking : ${fs.existsSync(destTracking) ? destTracking : 'no instalado'}`);
|
|
364
|
+
log();
|
|
365
|
+
|
|
366
|
+
const checks = [
|
|
367
|
+
['bash', ['--version']],
|
|
368
|
+
['git', ['--version']],
|
|
369
|
+
['python3', ['-c', 'import ipaddress; print("ipaddress OK")']],
|
|
370
|
+
['curl', ['--version']],
|
|
371
|
+
['terragrunt', ['--version']],
|
|
372
|
+
];
|
|
373
|
+
let blocking = 0;
|
|
374
|
+
for (const [cmd, args] of checks) {
|
|
375
|
+
const r = spawnSync(cmd, args, { encoding: 'utf8' });
|
|
376
|
+
const available = !r.error;
|
|
377
|
+
const optional = cmd === 'terragrunt';
|
|
378
|
+
if (available) {
|
|
379
|
+
const first = (r.stdout || '').trim().split('\n')[0];
|
|
380
|
+
ok(`${cmd}: ${first}`);
|
|
381
|
+
} else if (optional) {
|
|
382
|
+
warn(`${cmd}: no disponible (opcional — omite hcl fmt/validate al construir)`);
|
|
383
|
+
} else {
|
|
384
|
+
err(`${cmd}: NO disponible (requerido por los scripts del skill)`);
|
|
385
|
+
blocking++;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
log();
|
|
389
|
+
if (blocking) {
|
|
390
|
+
err(`${blocking} requisito(s) faltante(s) — el skill fallará sin ellos.`);
|
|
391
|
+
process.exit(1);
|
|
392
|
+
}
|
|
393
|
+
ok('Entorno listo para usar el skill.');
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// ---------------------------------------------------------------------------
|
|
397
|
+
// ayuda y main
|
|
398
|
+
// ---------------------------------------------------------------------------
|
|
399
|
+
|
|
400
|
+
function help() {
|
|
401
|
+
log(`${SKILL_NAME} v${VERSION} — instalador npx del skill`);
|
|
402
|
+
log();
|
|
403
|
+
log('Uso:');
|
|
404
|
+
log(` npx --yes ${SKILL_NAME} --target-repo /ruta/al/repo instalar (o actualizar)`);
|
|
405
|
+
log(` npx --yes ${SKILL_NAME} uninstall --target-repo /ruta desinstalar`);
|
|
406
|
+
log(` npx --yes ${SKILL_NAME} doctor --target-repo /ruta diagnóstico sin cambios`);
|
|
407
|
+
log();
|
|
408
|
+
log('Flags de install:');
|
|
409
|
+
log(' --target-repo RUTA raíz del repo destino (debe ser un repo git) [obligatorio]');
|
|
410
|
+
log(' --skip-tracking no instala/actualiza github-proyect-tracking');
|
|
411
|
+
log(' --force sobreescribe una instalación existente');
|
|
412
|
+
log();
|
|
413
|
+
log('Flags de uninstall:');
|
|
414
|
+
log(' --with-tracking elimina también github-proyect-tracking');
|
|
415
|
+
log(' --clean-gitignore quita .kcreate-scaffolding/ del .gitignore y borra la carpeta');
|
|
416
|
+
log();
|
|
417
|
+
log(' -h, --help esta ayuda');
|
|
418
|
+
log(' -v, --version versión del paquete');
|
|
419
|
+
log();
|
|
420
|
+
log('Destino: <repo>/.claude/skills/kcreate-scaffolding-gcp-script/ (+ github-proyect-tracking/).');
|
|
421
|
+
log('Requisitos: Node >= 18 para este instalador; bash, git, python3 3.8+, curl y');
|
|
422
|
+
log('GNU sed para los scripts; terragrunt opcional. Instalar el skill NO crea nada');
|
|
423
|
+
log('en GCP: solo prepara la herramienta.');
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function main() {
|
|
427
|
+
const { command, flags, rest } = parseArgs(process.argv.slice(2));
|
|
428
|
+
if (flags.version) { log(`${SKILL_NAME}/${VERSION} (instalador npx)`); return; }
|
|
429
|
+
if (flags.help) { help(); return; }
|
|
430
|
+
if (!COMMANDS.has(command)) die(`comando desconocido: ${command} (usa --help)`, 2);
|
|
431
|
+
flags.rest = rest;
|
|
432
|
+
validateFlags(command, flags);
|
|
433
|
+
|
|
434
|
+
switch (command) {
|
|
435
|
+
case 'install': install(flags); break;
|
|
436
|
+
case 'uninstall': uninstall(flags); break;
|
|
437
|
+
case 'doctor': doctor(flags); break;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kcreate-scaffolding-gcp-script",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Instalador npx del skill kcreate-scaffolding-gcp-script: genera el scaffolding de Terragrunt para un entorno GCP nuevo a partir de uno existente.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"kcreate-scaffolding-gcp-script": "bin/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin/",
|
|
10
|
+
"skill/",
|
|
11
|
+
"vendor/",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "node bin/build.js",
|
|
20
|
+
"prepack": "node bin/build.js"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"terragrunt",
|
|
24
|
+
"terraform",
|
|
25
|
+
"gcp",
|
|
26
|
+
"scaffolding",
|
|
27
|
+
"skill",
|
|
28
|
+
"claude-code",
|
|
29
|
+
"instalador"
|
|
30
|
+
],
|
|
31
|
+
"author": "Rommel (https://github.com/rommelmg)",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/kronops/KCreateScaffoldingGCPScripts.git"
|
|
36
|
+
},
|
|
37
|
+
"private": false
|
|
38
|
+
}
|