@jumpgroup/laravel-tools 3.3.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.
Files changed (78) hide show
  1. package/.claude/settings.local.json +59 -0
  2. package/README.md +378 -0
  3. package/bin/groups/cache.js +52 -0
  4. package/bin/groups/database.js +105 -0
  5. package/bin/groups/forge.js +272 -0
  6. package/bin/groups/local.js +78 -0
  7. package/bin/groups/media.js +110 -0
  8. package/bin/tools.js +23 -0
  9. package/docs/Changelog.md +267 -0
  10. package/docs/TODO.md +167 -0
  11. package/docs/releases/release_0.0.1.md +116 -0
  12. package/docs/releases/release_0.0.2.md +88 -0
  13. package/docs/releases/release_0.0.3.md +58 -0
  14. package/docs/releases/release_0.0.4.md +128 -0
  15. package/docs/releases/release_0.0.5.md +77 -0
  16. package/docs/releases/release_0.0.6.md +80 -0
  17. package/docs/releases/release_1.0.0.md +61 -0
  18. package/docs/releases/release_1.0.1.md +18 -0
  19. package/docs/releases/release_1.0.2.md +18 -0
  20. package/docs/releases/release_1.0.3.md +19 -0
  21. package/docs/releases/release_1.1.0.md +18 -0
  22. package/docs/releases/release_1.1.1.md +17 -0
  23. package/docs/releases/release_1.1.2.md +18 -0
  24. package/docs/releases/release_1.1.3.md +21 -0
  25. package/docs/releases/release_1.1.4.md +18 -0
  26. package/docs/releases/release_1.1.5.md +18 -0
  27. package/docs/releases/release_1.1.6.md +21 -0
  28. package/docs/releases/release_1.1.7.md +17 -0
  29. package/docs/releases/release_2.0.0.md +192 -0
  30. package/docs/releases/release_2.0.1.md +53 -0
  31. package/docs/releases/release_2.0.2.md +55 -0
  32. package/docs/releases/release_2.0.3.md +69 -0
  33. package/docs/releases/release_2.1.0.md +59 -0
  34. package/docs/releases/release_2.2.0.md +83 -0
  35. package/docs/releases/release_2.2.1.md +36 -0
  36. package/docs/releases/release_2.2.2.md +57 -0
  37. package/docs/releases/release_2.2.3.md +39 -0
  38. package/docs/releases/release_2.2.4.md +75 -0
  39. package/docs/releases/release_2.2.5.md +69 -0
  40. package/docs/releases/release_3.0.0.md +87 -0
  41. package/docs/releases/release_3.0.1.md +65 -0
  42. package/docs/releases/release_3.1.0.md +90 -0
  43. package/docs/releases/release_3.2.0.md +74 -0
  44. package/docs/releases/release_3.3.0.md +72 -0
  45. package/package.json +35 -0
  46. package/src/aws/bucket.js +287 -0
  47. package/src/aws/cloudfront.js +433 -0
  48. package/src/aws/config.js +39 -0
  49. package/src/aws/iam.js +189 -0
  50. package/src/cache.js +49 -0
  51. package/src/database.js +315 -0
  52. package/src/forge/client.js +43 -0
  53. package/src/forge/config.js +33 -0
  54. package/src/forge/provisioning.js +191 -0
  55. package/src/forge/servers.js +27 -0
  56. package/src/forge/sites.js +93 -0
  57. package/src/google/groupMembers.js +35 -0
  58. package/src/google/utilities.js +39 -0
  59. package/src/local/doctor.js +214 -0
  60. package/src/local/setup.js +398 -0
  61. package/src/media.js +143 -0
  62. package/src/stub/docker/mysql/my.cnf +6 -0
  63. package/src/stub/docker/php/local.ini +4 -0
  64. package/src/stub/docker/traefik/dynamic_conf.yml +4 -0
  65. package/src/stub/docker/traefik/traefik.yml +24 -0
  66. package/src/stub/docker-compose/php8.0/docker-compose.yml +78 -0
  67. package/src/stub/docker-compose/php8.1/docker-compose.yml +78 -0
  68. package/src/stub/docker-compose/php8.2/docker-compose.yml +78 -0
  69. package/src/stub/docker-compose/php8.3/docker-compose.yml +78 -0
  70. package/src/stub/docker-compose/php8.4/docker-compose.yml +78 -0
  71. package/src/stub/docker-compose.yml +78 -0
  72. package/src/utilities/command.js +137 -0
  73. package/src/utilities/dateUtils.js +7 -0
  74. package/src/utilities/fileUtils.js +36 -0
  75. package/src/utilities/google-drive.js +69 -0
  76. package/src/utilities/pathUtils.js +15 -0
  77. package/src/utilities/userInput.js +28 -0
  78. package/src/utilities/utilities.js +57 -0
@@ -0,0 +1,272 @@
1
+ import { input, select, confirm } from '@inquirer/prompts';
2
+ import { setForgeToken } from '../../src/forge/config.js';
3
+ import { listServers, pickServer, deleteServer } from '../../src/forge/servers.js';
4
+ import { pickSite, deploySite, getDeploymentLog, getEnv, setEnv, confirmDestructiveEnvWrite, createSite, pollSiteInstalled, installGitRepo, getDeployScript, updateDeployScript } from '../../src/forge/sites.js';
5
+ import { getAwsCredential, promptServerConfig, createServer, pollServerReady, readPhpVersionFromComposer, readAppName } from '../../src/forge/provisioning.js';
6
+
7
+ export default (program) => {
8
+ const forge = program.command('forge').description('Operazioni su Laravel Forge');
9
+
10
+ // forge token set
11
+ forge
12
+ .command('token')
13
+ .description('Gestione token Forge API')
14
+ .command('set')
15
+ .description('Salva il token Forge API in ~/.laravel-tools')
16
+ .action(async () => {
17
+ try {
18
+ const token = await input({ message: 'Forge API token:' });
19
+ setForgeToken(token.trim());
20
+ console.log('✅ Token salvato in ~/.laravel-tools');
21
+ } catch (error) {
22
+ console.error('❌ Errore:', error.message);
23
+ process.exit(1);
24
+ }
25
+ });
26
+
27
+ // forge servers
28
+ forge
29
+ .command('servers')
30
+ .description('Elenca i server Forge dell\'account')
31
+ .action(async () => {
32
+ try {
33
+ const servers = await listServers();
34
+ if (servers.length === 0) {
35
+ console.log('Nessun server trovato.');
36
+ return;
37
+ }
38
+ console.log('');
39
+ servers.forEach((s) => {
40
+ console.log(` ${s.name.padEnd(30)} ${s.ip_address.padEnd(18)} ${s.type}`);
41
+ });
42
+ console.log('');
43
+ } catch (error) {
44
+ console.error('❌ Errore:', error.message);
45
+ process.exit(1);
46
+ }
47
+ });
48
+
49
+ // forge server create
50
+ const server = forge.command('server').description('Gestione server Forge');
51
+
52
+ server
53
+ .command('create')
54
+ .description('Crea un nuovo server AWS Lightsail tramite Forge')
55
+ .action(async () => {
56
+ try {
57
+ console.log('🔑 Recupero credenziali AWS...');
58
+ const [credential, existingServers] = await Promise.all([
59
+ getAwsCredential(),
60
+ listServers(),
61
+ ]);
62
+ console.log(`✅ Credenziale trovata: ${credential.name}`);
63
+
64
+ const config = await promptServerConfig(credential.id, existingServers);
65
+
66
+ console.log(`\n🚀 Creazione server "${config.name}" in corso...`);
67
+ const newServer = await createServer(credential.id, config);
68
+ console.log(`✅ Server creato (ID: ${newServer.id}). Avvio provisioning...`);
69
+
70
+ const ready = await pollServerReady(newServer.id);
71
+ console.log(`\n✅ Server pronto!`);
72
+ console.log(` Nome: ${ready.name}`);
73
+ console.log(` IP: ${ready.ip_address}`);
74
+ console.log(` PHP: ${config.php_version}`);
75
+ console.log(` Redis: ${config.install_redis ? 'sì' : 'no'}\n`);
76
+ console.log(`Prossimo passo: laravel-tools forge site create`);
77
+ } catch (error) {
78
+ console.error('❌ Errore:', error.message);
79
+ process.exit(1);
80
+ }
81
+ });
82
+
83
+ server
84
+ .command('delete')
85
+ .description('Elimina un server Forge')
86
+ .action(async () => {
87
+ try {
88
+ const s = await pickServer();
89
+ console.log(`\n ⚠️ Stai per eliminare il server \x1b[31m${s.name}\x1b[0m (${s.ip_address})`);
90
+ console.log(` Questa operazione è \x1b[31mIRREVERSIBILE\x1b[0m.\n`);
91
+ const confirmed = await confirm({
92
+ message: `Confermi l'eliminazione di ${s.name}?`,
93
+ default: false,
94
+ });
95
+ if (!confirmed) {
96
+ console.log('Operazione annullata.');
97
+ return;
98
+ }
99
+ await deleteServer(s.id);
100
+ console.log(`✅ Server ${s.name} eliminato.`);
101
+ } catch (error) {
102
+ console.error('❌ Errore:', error.message);
103
+ process.exit(1);
104
+ }
105
+ });
106
+
107
+ // forge site create
108
+ const site = forge.command('site').description('Gestione siti Forge');
109
+
110
+ site
111
+ .command('create')
112
+ .description('Crea un sito su un server Forge e collega il repository Git')
113
+ .action(async () => {
114
+ try {
115
+ const { forgeVersion } = readPhpVersionFromComposer();
116
+ const appName = readAppName();
117
+ const dbDefault = appName.replace(/-/g, '_');
118
+
119
+ const server = await pickServer();
120
+
121
+ const domainDefault = server.name.includes('staging')
122
+ ? `api.${appName}.sitointest.it`
123
+ : `api.${appName}.it`;
124
+ const domain = await input({ message: 'Dominio del sito:', default: domainDefault });
125
+
126
+ const database = await input({
127
+ message: 'Nome database:',
128
+ default: dbDefault,
129
+ });
130
+
131
+ const provider = await select({
132
+ message: 'Provider Git:',
133
+ choices: [
134
+ { name: 'GitHub', value: 'github' },
135
+ { name: 'GitLab', value: 'gitlab' },
136
+ { name: 'Bitbucket', value: 'bitbucket' },
137
+ ],
138
+ });
139
+
140
+ const repository = await input({
141
+ message: 'Repository (org/repo):',
142
+ });
143
+
144
+ const branch = await input({
145
+ message: 'Branch:',
146
+ default: 'main',
147
+ });
148
+
149
+ console.log(`\n🌐 Creazione sito ${domain} su ${server.name}...`);
150
+ const newSite = await createSite(server.id, {
151
+ domain,
152
+ project_type: 'php',
153
+ directory: '/public',
154
+ php_version: forgeVersion,
155
+ database,
156
+ });
157
+
158
+ const installedSite = await pollSiteInstalled(server.id, newSite.id);
159
+ console.log(`✅ Sito installato (ID: ${installedSite.id})`);
160
+
161
+ console.log(`📦 Collegamento repository ${repository}...`);
162
+ await installGitRepo(server.id, installedSite.id, {
163
+ provider,
164
+ repository,
165
+ branch,
166
+ composer: true,
167
+ });
168
+
169
+ if (server.name.includes('staging')) {
170
+ console.log('🔧 Aggiunta migrate automatica al deploy script (staging)...');
171
+ const currentScript = await getDeployScript(server.id, installedSite.id);
172
+ const updatedScript = currentScript.trimEnd() + '\nphp artisan migrate --force\n';
173
+ await updateDeployScript(server.id, installedSite.id, updatedScript);
174
+ console.log('✅ Deploy script aggiornato.');
175
+ }
176
+
177
+ console.log(`\n✅ Sito pronto!`);
178
+ console.log(` Dominio: ${domain}`);
179
+ console.log(` Repository: ${repository} (${branch})`);
180
+ console.log(` Database: ${database}`);
181
+ console.log(` PHP: ${forgeVersion}\n`);
182
+ console.log(`Prossimi passi:`);
183
+ console.log(` 1. laravel-tools forge env set → configura il .env remoto`);
184
+ console.log(` 2. laravel-tools forge deploy → avvia il primo deploy\n`);
185
+ } catch (error) {
186
+ console.error('❌ Errore:', error.message);
187
+ process.exit(1);
188
+ }
189
+ });
190
+
191
+ // forge deploy
192
+ forge
193
+ .command('deploy')
194
+ .description('Avvia il deploy di un sito su Forge')
195
+ .action(async () => {
196
+ try {
197
+ const server = await pickServer();
198
+ const site = await pickSite(server.id);
199
+ console.log(`🚀 Avvio deploy di ${site.name}...`);
200
+ await deploySite(server.id, site.id);
201
+ console.log('✅ Deploy avviato. Monitora l\'avanzamento nella UI Forge.');
202
+ } catch (error) {
203
+ console.error('❌ Errore:', error.message);
204
+ process.exit(1);
205
+ }
206
+ });
207
+
208
+ // forge env get
209
+ const env = forge.command('env').description('Gestione .env remoto su Forge');
210
+
211
+ env
212
+ .command('get')
213
+ .description('Legge il file .env remoto di un sito')
214
+ .action(async () => {
215
+ try {
216
+ const server = await pickServer();
217
+ const site = await pickSite(server.id);
218
+ const content = await getEnv(server.id, site.id);
219
+ console.log('\n' + content);
220
+ } catch (error) {
221
+ console.error('❌ Errore:', error.message);
222
+ process.exit(1);
223
+ }
224
+ });
225
+
226
+ env
227
+ .command('set')
228
+ .description('Sovrascrive il file .env remoto di un sito')
229
+ .action(async () => {
230
+ try {
231
+ const server = await pickServer();
232
+ const site = await pickSite(server.id);
233
+
234
+ const confirmed = await confirmDestructiveEnvWrite();
235
+ if (!confirmed) {
236
+ console.log('Operazione annullata.');
237
+ return;
238
+ }
239
+
240
+ console.log('Incolla il contenuto del .env (termina con una riga contenente solo EOF):');
241
+ const lines = [];
242
+ process.stdin.setEncoding('utf8');
243
+ for await (const chunk of process.stdin) {
244
+ lines.push(chunk);
245
+ if (lines.join('').includes('\nEOF\n') || lines.join('').endsWith('\nEOF')) break;
246
+ }
247
+ const content = lines.join('').replace(/\nEOF$/, '').replace(/\nEOF\n$/, '');
248
+
249
+ await setEnv(server.id, site.id, content);
250
+ console.log('✅ .env remoto aggiornato.');
251
+ } catch (error) {
252
+ console.error('❌ Errore:', error.message);
253
+ process.exit(1);
254
+ }
255
+ });
256
+
257
+ // forge logs
258
+ forge
259
+ .command('logs')
260
+ .description('Mostra il log dell\'ultimo deploy di un sito')
261
+ .action(async () => {
262
+ try {
263
+ const server = await pickServer();
264
+ const site = await pickSite(server.id);
265
+ const log = await getDeploymentLog(server.id, site.id);
266
+ console.log('\n' + (log || 'Nessun log disponibile.'));
267
+ } catch (error) {
268
+ console.error('❌ Errore:', error.message);
269
+ process.exit(1);
270
+ }
271
+ });
272
+ };
@@ -0,0 +1,78 @@
1
+ import { Option } from 'commander';
2
+ import { setupProject, setupRepo, setupLaravel, addUser } from '../../src/local/setup.js';
3
+ import { runLocalDoctor } from '../../src/local/doctor.js';
4
+
5
+ export default (program) => {
6
+ const local = program.command('local').description('Operazioni per l\'ambiente di sviluppo locale');
7
+
8
+ local
9
+ .command('setup-project')
10
+ .description(
11
+ 'Definisce l\'identita\' del progetto locale e aggiorna APP_NAME, APP_URL e ASSETS_URL in .env.example'
12
+ )
13
+ .action(async () => {
14
+ try {
15
+ await setupProject();
16
+ } catch (error) {
17
+ console.error('❌ Errore:', error.message);
18
+ process.exit(1);
19
+ }
20
+ });
21
+
22
+ local
23
+ .command('setup-repo')
24
+ .description(
25
+ 'Prepara il repo locale: genera .env, copia gli stub Docker, crea i certificati e aggiorna /etc/hosts'
26
+ )
27
+ .addOption(
28
+ new Option('--phpversion <version>', 'Versione PHP da usare')
29
+ .choices(['php8.0', 'php8.1', 'php8.2', 'php8.3', 'php8.4'])
30
+ .default('php8.4')
31
+ )
32
+ .action(async (options) => {
33
+ try {
34
+ await setupRepo(options);
35
+ } catch (error) {
36
+ console.error('❌ Errore:', error.message);
37
+ process.exit(1);
38
+ }
39
+ });
40
+
41
+ const user = local.command('user').description('Gestione utenti del progetto locale');
42
+
43
+ user
44
+ .command('add')
45
+ .description('Crea un nuovo utente Filament nel container avviato')
46
+ .action(async () => {
47
+ try {
48
+ await addUser();
49
+ } catch (error) {
50
+ console.error('❌ Errore:', error.message);
51
+ process.exit(1);
52
+ }
53
+ });
54
+
55
+ local
56
+ .command('setup-laravel')
57
+ .description('Inizializza Laravel nel container avviato: chiave applicativa, migration e seeder opzionali')
58
+ .action(async () => {
59
+ try {
60
+ await setupLaravel();
61
+ } catch (error) {
62
+ console.error('❌ Errore:', error.message);
63
+ process.exit(1);
64
+ }
65
+ });
66
+
67
+ local
68
+ .command('doctor')
69
+ .description('Esegue controlli diagnostici su file, prerequisiti locali e stato Docker del progetto')
70
+ .action(async () => {
71
+ try {
72
+ await runLocalDoctor();
73
+ } catch (error) {
74
+ console.error('❌ Errore:', error.message);
75
+ process.exit(1);
76
+ }
77
+ });
78
+ };
@@ -0,0 +1,110 @@
1
+ import { Option } from 'commander';
2
+
3
+ import { getAllBuckets, getBucketFromTag } from '../../src/aws/bucket.js';
4
+ import {
5
+ getAllDistributions,
6
+ getDistributionByTagOrName,
7
+ createDistribution,
8
+ } from '../../src/aws/cloudfront.js';
9
+ import { setupAWS, setupIAM } from '../../src/media.js';
10
+
11
+ export default (program) => {
12
+ const media = program.command('media').description('Operazioni AWS media (S3 + CloudFront)');
13
+
14
+ media
15
+ .command('setup-general')
16
+ .description('Setup completo media: bucket S3, CloudFront e IAM user')
17
+ .addOption(new Option('-n, --name [name]', 'Nome progetto (default: APP_NAME)'))
18
+ .addOption(new Option('--dry-run', 'Mostra le operazioni AWS senza applicare modifiche').default(false))
19
+ .action(async (options) => {
20
+ try {
21
+ await setupAWS(options.name, { dryRun: options.dryRun });
22
+ } catch (error) {
23
+ console.error('❌ Errore:', error.message);
24
+ process.exit(1);
25
+ }
26
+ });
27
+
28
+ media
29
+ .command('setup-iam')
30
+ .description('Crea/aggiorna IAM user per accesso media')
31
+ .addOption(new Option('-n, --name [name]', 'Nome progetto (default: APP_NAME)'))
32
+ .addOption(new Option('-c, --cloudfront [cloudfront]', 'CloudFront distribution ID'))
33
+ .addOption(new Option('--dry-run', 'Mostra le operazioni AWS senza applicare modifiche').default(false))
34
+ .action(async (options) => {
35
+ try {
36
+ await setupIAM(options.name, options.cloudfront, { dryRun: options.dryRun });
37
+ } catch (error) {
38
+ console.error('❌ Errore:', error.message);
39
+ process.exit(1);
40
+ }
41
+ });
42
+
43
+ const s3 = media.command('s3').description('Comandi S3');
44
+
45
+ s3
46
+ .command('list')
47
+ .description('Lista bucket S3')
48
+ .action(async () => {
49
+ try {
50
+ await getAllBuckets(true);
51
+ } catch (error) {
52
+ console.error('❌ Errore:', error.message);
53
+ process.exit(1);
54
+ }
55
+ });
56
+
57
+ s3
58
+ .command('get')
59
+ .description('Trova bucket tramite tag site')
60
+ .addOption(new Option('-t, --tag [tag]', 'Tag site'))
61
+ .action(async (options) => {
62
+ try {
63
+ await getBucketFromTag(options.tag);
64
+ } catch (error) {
65
+ console.error('❌ Errore:', error.message);
66
+ process.exit(1);
67
+ }
68
+ });
69
+
70
+ const cloudfront = media.command('cloudfront').description('Comandi CloudFront');
71
+
72
+ cloudfront
73
+ .command('list')
74
+ .description('Lista distribuzioni CloudFront')
75
+ .action(async () => {
76
+ try {
77
+ await getAllDistributions(true);
78
+ } catch (error) {
79
+ console.error('❌ Errore:', error.message);
80
+ process.exit(1);
81
+ }
82
+ });
83
+
84
+ cloudfront
85
+ .command('get')
86
+ .description('Trova distribuzione per tag site (match esatto)')
87
+ .addOption(new Option('-t, --tag [tag]', 'Tag site progetto'))
88
+ .action(async (options) => {
89
+ try {
90
+ await getDistributionByTagOrName(options.tag);
91
+ } catch (error) {
92
+ console.error('❌ Errore:', error.message);
93
+ process.exit(1);
94
+ }
95
+ });
96
+
97
+ cloudfront
98
+ .command('setup')
99
+ .description('Crea distribuzione CloudFront per progetto')
100
+ .addOption(new Option('-n, --name [name]', 'Nome progetto (default: APP_NAME)'))
101
+ .addOption(new Option('--dry-run', 'Mostra le operazioni AWS senza applicare modifiche').default(false))
102
+ .action(async (options) => {
103
+ try {
104
+ await createDistribution(options.name, undefined, { dryRun: options.dryRun });
105
+ } catch (error) {
106
+ console.error('❌ Errore:', error.message);
107
+ process.exit(1);
108
+ }
109
+ });
110
+ };
package/bin/tools.js ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { program } from 'commander';
4
+ import { createRequire } from 'module';
5
+
6
+ import database from './groups/database.js';
7
+ import cache from './groups/cache.js';
8
+ import local from './groups/local.js';
9
+ import media from './groups/media.js';
10
+ import forge from './groups/forge.js';
11
+
12
+ const require = createRequire(import.meta.url);
13
+ const { version } = require('../package.json');
14
+
15
+ program.version(version);
16
+
17
+ database(program);
18
+ cache(program);
19
+ local(program);
20
+ media(program);
21
+ forge(program);
22
+
23
+ program.parse(process.argv);