@haxtheweb/create 11.0.7 → 25.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.
Files changed (24) hide show
  1. package/README.md +7 -0
  2. package/dist/create.js +9 -5
  3. package/dist/docs/hax.1 +248 -76
  4. package/dist/lib/programs/party.js +88 -42
  5. package/dist/lib/programs/site.js +319 -0
  6. package/dist/lib/wc-registry.json +1 -1
  7. package/dist/templates/sitedotfiles/_github_workflows_deploy.yml +63 -0
  8. package/dist/templates/sitedotfiles/_gitlab-ci.yml +62 -0
  9. package/dist/templates/sitedotfiles/_netlifyignore +24 -0
  10. package/dist/templates/sitedotfiles/_vercelignore +34 -0
  11. package/dist/templates/sitetheme/base-theme.js +9 -0
  12. package/dist/templates/webcomponent/hax/_github/workflows/main.yml +0 -8
  13. package/dist/templates/webcomponent/hax/locales/webcomponent.bn.haxProperties.json +16 -0
  14. package/dist/templates/webcomponent/hax/locales/webcomponent.bn.json +3 -0
  15. package/dist/templates/webcomponent/hax/locales/webcomponent.fr.haxProperties.json +16 -0
  16. package/dist/templates/webcomponent/hax/locales/webcomponent.fr.json +3 -0
  17. package/dist/templates/webcomponent/hax/locales/webcomponent.ja.haxProperties.json +16 -0
  18. package/dist/templates/webcomponent/hax/locales/webcomponent.ja.json +3 -0
  19. package/dist/templates/webcomponent/hax/locales/webcomponent.pt.haxProperties.json +16 -0
  20. package/dist/templates/webcomponent/hax/locales/webcomponent.pt.json +3 -0
  21. package/dist/templates/webcomponent/hax/locales/webcomponent.ru.haxProperties.json +16 -0
  22. package/dist/templates/webcomponent/hax/locales/webcomponent.ru.json +3 -0
  23. package/dist/templates/webcomponent/hax/webcomponent.js +0 -1
  24. package/package.json +3 -2
@@ -14,12 +14,19 @@ var _statements = require("../statements.js");
14
14
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
15
15
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
16
16
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
17
+ const osPathCmd = process.platform === "win32" ? "where.exe" : "which";
17
18
  let sysGit = true;
18
- (0, _utils.exec)('which git', error => {
19
+ (0, _utils.exec)(`${osPathCmd} git`, error => {
19
20
  if (error) {
20
21
  sysGit = false;
21
22
  }
22
23
  });
24
+ let sysGh = true;
25
+ (0, _utils.exec)(`${osPathCmd} gh`, error => {
26
+ if (error) {
27
+ sysGh = false;
28
+ }
29
+ });
23
30
  let sysSSH = true;
24
31
  function partyActions() {
25
32
  return [{
@@ -71,7 +78,8 @@ async function partyCommandDetected(commandRun) {
71
78
  command: null,
72
79
  arguments: {},
73
80
  options: {
74
- // npmClient: `${operation.npmClient}`
81
+ npmClient: `${operation.npmClient}`,
82
+ author: `${operation.author}`
75
83
  }
76
84
  };
77
85
  operation = await p.group({
@@ -235,7 +243,6 @@ async function partyCommandDetected(commandRun) {
235
243
  }
236
244
  async function cloneHAXRepositories(commandRun) {
237
245
  let s = p.spinner();
238
-
239
246
  // check for system dependencies: ssh, yarn, etc.
240
247
  if (!sysGit) {
241
248
  console.error(`${_picocolors.default.red(`Git is not installed. The Git CLI is required to access GitHub with ${_picocolors.default.bold('hax party')}.`)}
@@ -243,18 +250,34 @@ async function cloneHAXRepositories(commandRun) {
243
250
  ${_picocolors.default.underline(_picocolors.default.cyan(`https://git-scm.com/book/en/v2/Getting-Started-Installing-Git`))}`);
244
251
  process.exit(1);
245
252
  }
246
- await (0, _utils.interactiveExec)('ssh -T git@github.com', (error, stdout, stderr) => {
247
- const output = stdout + stderr;
248
- // The GitHub SSH test always returns as stderr
249
- if (!output.includes('successfully authenticated')) {
250
- sysSSH = false;
253
+ if (!sysGh) {
254
+ try {
255
+ const {
256
+ stdout,
257
+ stderr
258
+ } = await (0, _utils.exec)('ssh -T git@github.com');
259
+ } catch (error) {
260
+ // The GitHub SSH test always returns as stderr
261
+ if (!error.stderr.includes('successfully authenticated')) {
262
+ sysSSH = false;
263
+ }
264
+ }
265
+ if (!sysSSH) {
266
+ console.error(`${_picocolors.default.red(`SSH keys are not set up correctly. SSH is required to access GitHub with ${_picocolors.default.bold('hax party')}.`)}
267
+ Please follow the instructions at:
268
+ ${_picocolors.default.underline(_picocolors.default.cyan(`https://docs.github.com/en/authentication/connecting-to-github-with-ssh`))}`);
269
+ process.exit(1);
270
+ }
271
+ } else {
272
+ // gh cli can make new ssh keys and push them to GitHub
273
+ try {
274
+ await (0, _utils.exec)('gh auth status');
275
+ } catch {
276
+ console.log(`${_picocolors.default.red(`You have GitHub CLI installed, but it is not properly authenticated.`)}
277
+ To access GitHub with ${_picocolors.default.bold('hax party')}, please follow this guided login:
278
+ Select the ${_picocolors.default.cyan(`Generate a new SSH key`)} and ${_picocolors.default.cyan(`Login with a web browser`)} options`);
279
+ await (0, _utils.interactiveExec)('gh', ['auth', 'login', '-p', 'ssh', '--clipboard']);
251
280
  }
252
- });
253
- if (!sysSSH) {
254
- console.error(`${_picocolors.default.red(`SSH keys are not set up correctly. SSH is required to access GitHub with ${_picocolors.default.bold('hax party')}.`)}
255
- Please follow the instructions at:
256
- ${_picocolors.default.underline(_picocolors.default.cyan(`https://docs.github.com/en/authentication/connecting-to-github-with-ssh`))}`);
257
- process.exit(1);
258
281
  }
259
282
  try {
260
283
  await (0, _utils.exec)(`yarn --version`);
@@ -262,20 +285,13 @@ async function cloneHAXRepositories(commandRun) {
262
285
  await (0, _utils.exec)(`npm install -g yarn`);
263
286
  }
264
287
  for (const item of commandRun.options.repos) {
265
- // while loop keeps HAX active until the user is ready
266
- let isForked = false;
267
- let firstRun = true;
268
- while (!isForked) {
288
+ // If GitHub CLI is installed
289
+ if (sysGh) {
269
290
  try {
270
291
  // ssh link is used since https prompts for password
271
- if (firstRun) {
272
- s.start(`Cloning ${_picocolors.default.bold(item)} to ${_picocolors.default.bold(process.cwd())}`);
273
- } else {
274
- s.start(`Trying again... Cloning ${item} to ${_picocolors.default.bold(process.cwd())}`);
275
- }
292
+ s.start(`Cloning ${_picocolors.default.bold(item)} to ${_picocolors.default.bold(process.cwd())}`);
276
293
  await (0, _utils.exec)(`git clone git@github.com:${commandRun.options.author}/${item}.git`);
277
294
  s.stop(`${_picocolors.default.green("Successfully")} cloned ${_picocolors.default.bold(item)} to ${_picocolors.default.bold(process.cwd())}`);
278
- isForked = true;
279
295
  } catch (e) {
280
296
  // skip the loop if the repo already exists
281
297
  if (e.stderr.includes("already exists and is not an empty directory")) {
@@ -283,26 +299,56 @@ async function cloneHAXRepositories(commandRun) {
283
299
  break;
284
300
  }
285
301
  s.stop(`${_picocolors.default.red("Failed")} to clone ${_picocolors.default.bold(item)} to ${_picocolors.default.bold(process.cwd())}`);
286
- p.note(`${_picocolors.default.red(`Error: HAX cannot find a personal ${_picocolors.default.bold("fork")} of the ${_picocolors.default.bold(item)} repository on your GitHub account`)}
287
- Use the following link to fork ${_picocolors.default.bold(item)}: ${_picocolors.default.underline(_picocolors.default.cyan(`https://github.com/haxtheweb/${item}/fork`))}`);
302
+ p.note(`${_picocolors.default.red(`Error: HAX could not find your personal ${_picocolors.default.bold("fork")} of the ${_picocolors.default.bold(item)} repository on GitHub`)}`);
303
+ s.start(`Using ${_picocolors.default.bold(`gh repo`)} utility to fork and clone ${_picocolors.default.bold(item)}`);
304
+ await (0, _utils.exec)(`gh repo fork haxtheweb/${item} --clone`);
305
+ s.stop(`${_picocolors.default.green("Successfully")} cloned ${_picocolors.default.bold(item)} to ${_picocolors.default.bold(process.cwd())}`);
306
+ }
307
+ }
308
+ // Standard git, not GitHub CLI
309
+ else {
310
+ // while loop keeps HAX active until the user is ready
311
+ let isForked = false;
312
+ let firstRun = true;
313
+ while (!isForked) {
314
+ try {
315
+ // ssh link is used since https prompts for password
316
+ if (firstRun) {
317
+ s.start(`Cloning ${_picocolors.default.bold(item)} to ${_picocolors.default.bold(process.cwd())}`);
318
+ } else {
319
+ s.start(`Trying again... Cloning ${item} to ${_picocolors.default.bold(process.cwd())}`);
320
+ }
321
+ await (0, _utils.exec)(`git clone git@github.com:${commandRun.options.author}/${item}.git`);
322
+ s.stop(`${_picocolors.default.green("Successfully")} cloned ${_picocolors.default.bold(item)} to ${_picocolors.default.bold(process.cwd())}`);
323
+ isForked = true;
324
+ } catch (e) {
325
+ // skip the loop if the repo already exists
326
+ if (e.stderr.includes("already exists and is not an empty directory")) {
327
+ s.stop(`${_picocolors.default.yellow(`${_picocolors.default.bold(`${item}`)} already exists in ${_picocolors.default.bold(process.cwd())}`)}`);
328
+ break;
329
+ }
330
+ s.stop(`${_picocolors.default.red("Failed")} to clone ${_picocolors.default.bold(item)} to ${_picocolors.default.bold(process.cwd())}`);
331
+ p.note(`${_picocolors.default.red(`Error: HAX cannot find a personal ${_picocolors.default.bold("fork")} of the ${_picocolors.default.bold(item)} repository on your GitHub account`)}
332
+ Use the following link to fork ${_picocolors.default.bold(item)}: ${_picocolors.default.underline(_picocolors.default.cyan(`https://github.com/haxtheweb/${item}/fork`))}`);
288
333
 
289
- // We don't want to spam the link every time
290
- if (firstRun) {
291
- p.intro(`${(0, _statements.merlinSays)("The link will open in your browser in a few seconds")}`);
292
- setTimeout(async () => {
293
- await (0, _open.default)(`https://github.com/haxtheweb/${item}/fork`);
294
- }, 3000);
295
- firstRun = false;
296
- }
297
- let response = await p.confirm({
298
- message: `Have you forked the repository? Would you like to try again?`,
299
- initialValue: true
300
- });
334
+ // We don't want to spam the link every time
335
+ if (firstRun) {
336
+ p.intro(`${(0, _statements.merlinSays)("The link will open in your browser in a few seconds")}`);
337
+ setTimeout(async () => {
338
+ await (0, _open.default)(`https://github.com/haxtheweb/${item}/fork`);
339
+ }, 3000);
340
+ firstRun = false;
341
+ }
342
+ let response = await p.confirm({
343
+ message: `Have you forked the repository? Would you like to try again?`,
344
+ initialValue: true
345
+ });
301
346
 
302
- // Multiple ways to quit (select no, ctrl+c, etc)
303
- if (p.isCancel(response) || !response) {
304
- p.cancel('🧙 Merlin: Canceling CLI.. HAX ya later 🪄');
305
- process.exit(0);
347
+ // Multiple ways to quit (select no, ctrl+c, etc)
348
+ if (p.isCancel(response) || !response) {
349
+ p.cancel('🧙 Merlin: Canceling CLI.. HAX ya later 🪄');
350
+ process.exit(0);
351
+ }
306
352
  }
307
353
  }
308
354
  }
@@ -45,6 +45,24 @@ var sysSurge = true;
45
45
  sysSurge = false;
46
46
  }
47
47
  });
48
+ var sysNetlify = true;
49
+ (0, _utils.exec)('netlify --version', error => {
50
+ if (error) {
51
+ sysNetlify = false;
52
+ }
53
+ });
54
+ var sysVercel = true;
55
+ (0, _utils.exec)('vercel --version', error => {
56
+ if (error) {
57
+ sysVercel = false;
58
+ }
59
+ });
60
+ var sysRsync = true;
61
+ (0, _utils.exec)('rsync --version', error => {
62
+ if (error) {
63
+ sysRsync = false;
64
+ }
65
+ });
48
66
  const siteRecipeFile = 'create-cli.recipe';
49
67
  const siteLoggingName = 'cli';
50
68
  const logLevels = {};
@@ -123,9 +141,24 @@ function siteActions() {
123
141
  }, {
124
142
  value: 'site:sync',
125
143
  label: "Sync git repo"
144
+ }, {
145
+ value: 'site:rsync',
146
+ label: "Rsync site to remote/local directory"
126
147
  }, {
127
148
  value: 'site:surge',
128
149
  label: "Publish site to Surge.sh"
150
+ }, {
151
+ value: 'site:netlify',
152
+ label: "Publish site to Netlify"
153
+ }, {
154
+ value: 'site:vercel',
155
+ label: "Publish site to Vercel"
156
+ }, {
157
+ value: 'setup:github-actions',
158
+ label: "Setup GitHub Actions deployment"
159
+ }, {
160
+ value: 'setup:gitlab-ci',
161
+ label: "Setup GitLab CI deployment"
129
162
  }, {
130
163
  value: 'recipe:read',
131
164
  label: "Read recipe file"
@@ -765,6 +798,142 @@ async function siteCommandDetected(commandRun) {
765
798
  (0, _logging.log)(e.stderr);
766
799
  }
767
800
  break;
801
+ case "site:rsync":
802
+ try {
803
+ if (!sysRsync) {
804
+ if (!commandRun.options.quiet) {
805
+ p.intro(`${_picocolors.default.bgRed(_picocolors.default.white(` ERROR: rsync not found `))}`);
806
+ p.outro(`${_picocolors.default.red('rsync is required but not installed on this system.')}`);
807
+ p.outro(`${_picocolors.default.yellow('Install rsync:')}`);
808
+ p.outro(`${_picocolors.default.gray(' Ubuntu/Debian: sudo apt install rsync')}`);
809
+ p.outro(`${_picocolors.default.gray(' macOS: brew install rsync')}`);
810
+ p.outro(`${_picocolors.default.gray(' CentOS/RHEL: sudo yum install rsync')}`);
811
+ }
812
+ break;
813
+ }
814
+ let source = commandRun.options.source || activeHaxsite.directory;
815
+ let destination = commandRun.options.destination;
816
+ let excludePatterns = commandRun.options.exclude ? commandRun.options.exclude.split(',').map(p => p.trim()) : ['node_modules', '.git', '.DS_Store', 'dist', 'build'];
817
+ let dryRun = commandRun.options.dryRun || false;
818
+
819
+ // Interactive prompts if not provided via CLI
820
+ if (!commandRun.options.y && !destination) {
821
+ let action = await p.select({
822
+ message: 'Rsync action:',
823
+ options: [{
824
+ value: 'to-remote',
825
+ label: 'Sync site to remote server'
826
+ }, {
827
+ value: 'to-local',
828
+ label: 'Sync site to local directory'
829
+ }, {
830
+ value: 'from-remote',
831
+ label: 'Sync from remote server to site'
832
+ }, {
833
+ value: 'test',
834
+ label: 'Test sync (dry run)'
835
+ }]
836
+ });
837
+ if (action === 'test') {
838
+ dryRun = true;
839
+ }
840
+ if (action === 'from-remote') {
841
+ source = await p.text({
842
+ message: 'Source (user@host:/path):',
843
+ placeholder: 'user@example.com:/var/www/html',
844
+ validate: value => {
845
+ if (!value) return 'Source is required';
846
+ }
847
+ });
848
+ destination = activeHaxsite.directory;
849
+ } else {
850
+ destination = await p.text({
851
+ message: action === 'to-remote' ? 'Destination (user@host:/path):' : 'Destination directory:',
852
+ placeholder: action === 'to-remote' ? 'user@example.com:/var/www/html' : '/backup/location',
853
+ validate: value => {
854
+ if (!value) return 'Destination is required';
855
+ }
856
+ });
857
+ }
858
+ let excludeInput = await p.text({
859
+ message: 'Exclude patterns (comma-separated):',
860
+ placeholder: 'node_modules,.git,.DS_Store,dist,build',
861
+ initialValue: 'node_modules,.git,.DS_Store,dist,build'
862
+ });
863
+ if (excludeInput) {
864
+ excludePatterns = excludeInput.split(',').map(p => p.trim());
865
+ }
866
+ if (!dryRun && action !== 'test') {
867
+ dryRun = await p.confirm({
868
+ message: 'Perform dry run first?',
869
+ initialValue: true
870
+ });
871
+ }
872
+ }
873
+ if (!destination) {
874
+ if (!commandRun.options.quiet) {
875
+ p.intro(`${_picocolors.default.bgRed(_picocolors.default.white(` ERROR: destination required `))}`);
876
+ }
877
+ break;
878
+ }
879
+
880
+ // Build rsync command
881
+ let rsyncArgs = ['-avz',
882
+ // archive, verbose, compress
883
+ '--progress',
884
+ // show progress
885
+ '--stats' // show stats
886
+ ];
887
+
888
+ // Add dry run flag if requested
889
+ if (dryRun) {
890
+ rsyncArgs.push('--dry-run');
891
+ }
892
+
893
+ // Add exclude patterns
894
+ excludePatterns.forEach(pattern => {
895
+ rsyncArgs.push('--exclude', pattern);
896
+ });
897
+
898
+ // Add delete flag to mirror source (be careful with this)
899
+ if (commandRun.options.delete) {
900
+ rsyncArgs.push('--delete');
901
+ }
902
+
903
+ // Add source and destination
904
+ // Ensure source ends with / for directory contents
905
+ if (!source.endsWith('/') && fs.lstatSync(source).isDirectory()) {
906
+ source += '/';
907
+ }
908
+ rsyncArgs.push(source, destination);
909
+ if (!commandRun.options.quiet) {
910
+ p.intro(`${dryRun ? _picocolors.default.yellow('🧪 Dry run: ') : _picocolors.default.green('🚀 Running: ')}rsync ${rsyncArgs.join(' ')}`);
911
+ }
912
+ if (commandRun.options.i && !commandRun.options.quiet) {
913
+ // Interactive execution for real-time progress
914
+ await (0, _utils.interactiveExec)('rsync', rsyncArgs);
915
+ } else {
916
+ // Silent execution
917
+ const result = await (0, _utils.exec)(`rsync ${rsyncArgs.join(' ')}`);
918
+ if (!commandRun.options.quiet && result.stdout) {
919
+ console.log(result.stdout);
920
+ }
921
+ if (result.stderr) {
922
+ console.error(result.stderr);
923
+ }
924
+ }
925
+ recipe.log(siteLoggingName, (0, _logging.commandString)(commandRun));
926
+ if (!commandRun.options.quiet) {
927
+ p.outro(`${_picocolors.default.green('✓')} ${dryRun ? 'Dry run completed' : 'Rsync completed successfully'}`);
928
+ }
929
+ } catch (e) {
930
+ (0, _logging.log)(`Rsync error: ${e.message}`, 'error');
931
+ if (!commandRun.options.quiet) {
932
+ p.intro(`${_picocolors.default.bgRed(_picocolors.default.white(` Rsync Error `))}`);
933
+ p.outro(`${_picocolors.default.red('✗')} ${e.message}`);
934
+ }
935
+ }
936
+ break;
768
937
  case "site:theme":
769
938
  try {
770
939
  //theme
@@ -1013,6 +1182,150 @@ async function siteCommandDetected(commandRun) {
1013
1182
  (0, _logging.log)(e.stderr);
1014
1183
  }
1015
1184
  break;
1185
+ case "site:netlify":
1186
+ try {
1187
+ // attempt to install; implies they asked to publish with netlify but
1188
+ // system test did not see it globally
1189
+ if (!sysNetlify) {
1190
+ let s = p.spinner();
1191
+ s.start((0, _statements.merlinSays)('Installing Netlify CLI globally so we can publish'));
1192
+ let execOutput = await (0, _utils.exec)(`npm install --global netlify-cli`);
1193
+ s.stop((0, _statements.merlinSays)('Netlify CLI installed globally'));
1194
+ (0, _logging.log)(execOutput.stdout.trim());
1195
+ sysNetlify = true;
1196
+ }
1197
+ let execOutput;
1198
+ if (commandRun.options.y) {
1199
+ let s = p.spinner();
1200
+ s.start((0, _statements.merlinSays)('Deploying site to Netlify ..'));
1201
+ if (commandRun.options.domain) {
1202
+ // If specific site/domain is specified, deploy to existing site
1203
+ execOutput = await (0, _utils.exec)(`cd ${activeHaxsite.directory} && netlify deploy --prod --site ${commandRun.options.domain}`);
1204
+ } else {
1205
+ // Auto deploy - will create a new site or use existing site config
1206
+ execOutput = await (0, _utils.exec)(`cd ${activeHaxsite.directory} && netlify deploy --prod`);
1207
+ }
1208
+ (0, _logging.log)(execOutput.stdout.trim());
1209
+ s.stop((0, _statements.merlinSays)(`Site deployed to Netlify`));
1210
+ } else {
1211
+ let netlifyArgs = ['deploy', '--prod'];
1212
+ if (commandRun.options.domain) {
1213
+ netlifyArgs.push('--site', commandRun.options.domain);
1214
+ }
1215
+ execOutput = await (0, _utils.interactiveExec)('netlify', netlifyArgs, {
1216
+ cwd: activeHaxsite.directory
1217
+ });
1218
+ (0, _logging.log)((0, _statements.merlinSays)(`Site deployed to Netlify`));
1219
+ }
1220
+ } catch (e) {
1221
+ console.log("?");
1222
+ (0, _logging.log)(e.stderr);
1223
+ }
1224
+ break;
1225
+ case "site:vercel":
1226
+ try {
1227
+ // attempt to install; implies they asked to publish with vercel but
1228
+ // system test did not see it globally
1229
+ if (!sysVercel) {
1230
+ let s = p.spinner();
1231
+ s.start((0, _statements.merlinSays)('Installing Vercel CLI globally so we can publish'));
1232
+ let execOutput = await (0, _utils.exec)(`npm install --global vercel`);
1233
+ s.stop((0, _statements.merlinSays)('Vercel CLI installed globally'));
1234
+ (0, _logging.log)(execOutput.stdout.trim());
1235
+ sysVercel = true;
1236
+ }
1237
+ let execOutput;
1238
+ if (commandRun.options.y) {
1239
+ let s = p.spinner();
1240
+ s.start((0, _statements.merlinSays)('Deploying site to Vercel ..'));
1241
+ if (commandRun.options.domain) {
1242
+ // Deploy with specific domain/project name
1243
+ execOutput = await (0, _utils.exec)(`cd ${activeHaxsite.directory} && vercel --prod --name ${commandRun.options.domain}`);
1244
+ } else {
1245
+ // Auto deploy with default settings
1246
+ execOutput = await (0, _utils.exec)(`cd ${activeHaxsite.directory} && vercel --prod`);
1247
+ }
1248
+ (0, _logging.log)(execOutput.stdout.trim());
1249
+ s.stop((0, _statements.merlinSays)(`Site deployed to Vercel`));
1250
+ } else {
1251
+ let vercelArgs = ['--prod'];
1252
+ if (commandRun.options.domain) {
1253
+ vercelArgs.push('--name', commandRun.options.domain);
1254
+ }
1255
+ execOutput = await (0, _utils.interactiveExec)('vercel', vercelArgs, {
1256
+ cwd: activeHaxsite.directory
1257
+ });
1258
+ (0, _logging.log)((0, _statements.merlinSays)(`Site deployed to Vercel`));
1259
+ }
1260
+ } catch (e) {
1261
+ console.log("?");
1262
+ (0, _logging.log)(e.stderr);
1263
+ }
1264
+ break;
1265
+ case "setup:github-actions":
1266
+ try {
1267
+ let s = p.spinner();
1268
+ s.start((0, _statements.merlinSays)('Setting up GitHub Actions deployment workflow'));
1269
+
1270
+ // Create .github/workflows directory
1271
+ const workflowDir = path.join(activeHaxsite.directory, '.github', 'workflows');
1272
+ if (!fs.existsSync(workflowDir)) {
1273
+ fs.mkdirSync(workflowDir, {
1274
+ recursive: true
1275
+ });
1276
+ }
1277
+
1278
+ // Copy the workflow file
1279
+ const workflowFile = path.join(workflowDir, 'deploy.yml');
1280
+ if (fs.existsSync(workflowFile) && !commandRun.options.y) {
1281
+ s.stop((0, _statements.merlinSays)('GitHub Actions workflow already exists'));
1282
+ let overwrite = await p.confirm({
1283
+ message: 'GitHub Actions workflow file already exists. Overwrite?',
1284
+ initialValue: false
1285
+ });
1286
+ if (!overwrite) {
1287
+ (0, _logging.log)('Skipped GitHub Actions setup');
1288
+ break;
1289
+ }
1290
+ }
1291
+ await fs.copyFileSync(path.join(process.mainModule.path, 'templates/sitedotfiles/_github_workflows_deploy.yml'), workflowFile);
1292
+ s.stop((0, _statements.merlinSays)('GitHub Actions workflow created successfully'));
1293
+ if (!commandRun.options.quiet) {
1294
+ p.note(`🚀 GitHub Actions workflow has been set up!\n\nNext steps:\n1. Push your changes: ${_picocolors.default.bold('git add . && git commit -m "Add GitHub Actions workflow" && git push')}\n2. Enable GitHub Pages in your repository settings\n3. Select "GitHub Actions" as the source\n4. Your site will automatically deploy on every push to main/master`);
1295
+ }
1296
+ } catch (e) {
1297
+ console.log("?");
1298
+ (0, _logging.log)(e.stderr);
1299
+ }
1300
+ break;
1301
+ case "setup:gitlab-ci":
1302
+ try {
1303
+ let s = p.spinner();
1304
+ s.start((0, _statements.merlinSays)('Setting up GitLab CI deployment pipeline'));
1305
+
1306
+ // Copy the GitLab CI file
1307
+ const ciFile = path.join(activeHaxsite.directory, '.gitlab-ci.yml');
1308
+ if (fs.existsSync(ciFile) && !commandRun.options.y) {
1309
+ s.stop((0, _statements.merlinSays)('GitLab CI file already exists'));
1310
+ let overwrite = await p.confirm({
1311
+ message: '.gitlab-ci.yml already exists. Overwrite?',
1312
+ initialValue: false
1313
+ });
1314
+ if (!overwrite) {
1315
+ (0, _logging.log)('Skipped GitLab CI setup');
1316
+ break;
1317
+ }
1318
+ }
1319
+ await fs.copyFileSync(path.join(process.mainModule.path, 'templates/sitedotfiles/_gitlab-ci.yml'), ciFile);
1320
+ s.stop((0, _statements.merlinSays)('GitLab CI pipeline created successfully'));
1321
+ if (!commandRun.options.quiet) {
1322
+ p.note(`🚀 GitLab CI pipeline has been set up!\n\nNext steps:\n1. Push your changes: ${_picocolors.default.bold('git add . && git commit -m "Add GitLab CI pipeline" && git push')}\n2. GitLab Pages will be automatically enabled\n3. Your site will deploy on every push to main/master\n4. Access your site at: ${_picocolors.default.cyan('https://yourusername.gitlab.io/yourproject')}`);
1323
+ }
1324
+ } catch (e) {
1325
+ console.log("?");
1326
+ (0, _logging.log)(e.stderr);
1327
+ }
1328
+ break;
1016
1329
  case "site:file-list":
1017
1330
  let res = new Res();
1018
1331
  await hax.RoutesMap.get.listFiles({
@@ -1495,6 +1808,12 @@ async function siteProcess(commandRun, project, port = '3000') {
1495
1808
  if (!fs.existsSync(`${project.path}/${project.name}/._surgeignore`)) {
1496
1809
  await fs.copyFileSync(`${process.mainModule.path}/templates/sitedotfiles/_surgeignore`, `${project.path}/${project.name}/.surgeignore`);
1497
1810
  }
1811
+ if (!fs.existsSync(`${project.path}/${project.name}/.netlifyignore`)) {
1812
+ await fs.copyFileSync(`${process.mainModule.path}/templates/sitedotfiles/_netlifyignore`, `${project.path}/${project.name}/.netlifyignore`);
1813
+ }
1814
+ if (!fs.existsSync(`${project.path}/${project.name}/.vercelignore`)) {
1815
+ await fs.copyFileSync(`${process.mainModule.path}/templates/sitedotfiles/_vercelignore`, `${project.path}/${project.name}/.vercelignore`);
1816
+ }
1498
1817
  // options for install, git and other extras
1499
1818
  // can't launch if we didn't install first so launch implies installation
1500
1819
  if (project.extras && project.extras.includes && project.extras.includes('launch')) {