@developers-homelab/runestone-cli 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.
Files changed (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +107 -0
  3. package/bin/runestone +17 -0
  4. package/dist/cli.d.ts +4 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +78 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/commands/certs.d.ts +3 -0
  9. package/dist/commands/certs.d.ts.map +1 -0
  10. package/dist/commands/certs.js +110 -0
  11. package/dist/commands/certs.js.map +1 -0
  12. package/dist/commands/down.d.ts +3 -0
  13. package/dist/commands/down.d.ts.map +1 -0
  14. package/dist/commands/down.js +59 -0
  15. package/dist/commands/down.js.map +1 -0
  16. package/dist/commands/keys.d.ts +3 -0
  17. package/dist/commands/keys.d.ts.map +1 -0
  18. package/dist/commands/keys.js +51 -0
  19. package/dist/commands/keys.js.map +1 -0
  20. package/dist/commands/setup.d.ts +15 -0
  21. package/dist/commands/setup.d.ts.map +1 -0
  22. package/dist/commands/setup.js +641 -0
  23. package/dist/commands/setup.js.map +1 -0
  24. package/dist/commands/status.d.ts +3 -0
  25. package/dist/commands/status.d.ts.map +1 -0
  26. package/dist/commands/status.js +57 -0
  27. package/dist/commands/status.js.map +1 -0
  28. package/dist/commands/stop.d.ts +3 -0
  29. package/dist/commands/stop.d.ts.map +1 -0
  30. package/dist/commands/stop.js +25 -0
  31. package/dist/commands/stop.js.map +1 -0
  32. package/dist/commands/up.d.ts +3 -0
  33. package/dist/commands/up.d.ts.map +1 -0
  34. package/dist/commands/up.js +72 -0
  35. package/dist/commands/up.js.map +1 -0
  36. package/dist/i18n/index.d.ts +316 -0
  37. package/dist/i18n/index.d.ts.map +1 -0
  38. package/dist/i18n/index.js +378 -0
  39. package/dist/i18n/index.js.map +1 -0
  40. package/dist/services/cert-manager.d.ts +38 -0
  41. package/dist/services/cert-manager.d.ts.map +1 -0
  42. package/dist/services/cert-manager.js +260 -0
  43. package/dist/services/cert-manager.js.map +1 -0
  44. package/dist/services/docker-compose.d.ts +24 -0
  45. package/dist/services/docker-compose.d.ts.map +1 -0
  46. package/dist/services/docker-compose.js +132 -0
  47. package/dist/services/docker-compose.js.map +1 -0
  48. package/dist/services/docker-exec.d.ts +10 -0
  49. package/dist/services/docker-exec.d.ts.map +1 -0
  50. package/dist/services/docker-exec.js +39 -0
  51. package/dist/services/docker-exec.js.map +1 -0
  52. package/dist/services/docker-network.d.ts +13 -0
  53. package/dist/services/docker-network.d.ts.map +1 -0
  54. package/dist/services/docker-network.js +58 -0
  55. package/dist/services/docker-network.js.map +1 -0
  56. package/dist/services/docker-run.d.ts +18 -0
  57. package/dist/services/docker-run.d.ts.map +1 -0
  58. package/dist/services/docker-run.js +56 -0
  59. package/dist/services/docker-run.js.map +1 -0
  60. package/dist/services/docker-volume.d.ts +12 -0
  61. package/dist/services/docker-volume.d.ts.map +1 -0
  62. package/dist/services/docker-volume.js +57 -0
  63. package/dist/services/docker-volume.js.map +1 -0
  64. package/dist/services/domain-checker.d.ts +29 -0
  65. package/dist/services/domain-checker.d.ts.map +1 -0
  66. package/dist/services/domain-checker.js +149 -0
  67. package/dist/services/domain-checker.js.map +1 -0
  68. package/dist/services/port-checker.d.ts +3 -0
  69. package/dist/services/port-checker.d.ts.map +1 -0
  70. package/dist/services/port-checker.js +45 -0
  71. package/dist/services/port-checker.js.map +1 -0
  72. package/dist/services/ssh-manager.d.ts +7 -0
  73. package/dist/services/ssh-manager.d.ts.map +1 -0
  74. package/dist/services/ssh-manager.js +125 -0
  75. package/dist/services/ssh-manager.js.map +1 -0
  76. package/dist/utils/command.d.ts +6 -0
  77. package/dist/utils/command.d.ts.map +1 -0
  78. package/dist/utils/command.js +34 -0
  79. package/dist/utils/command.js.map +1 -0
  80. package/dist/utils/docker-checker.d.ts +5 -0
  81. package/dist/utils/docker-checker.d.ts.map +1 -0
  82. package/dist/utils/docker-checker.js +31 -0
  83. package/dist/utils/docker-checker.js.map +1 -0
  84. package/dist/utils/env-loader.d.ts +32 -0
  85. package/dist/utils/env-loader.d.ts.map +1 -0
  86. package/dist/utils/env-loader.js +113 -0
  87. package/dist/utils/env-loader.js.map +1 -0
  88. package/dist/utils/logger.d.ts +8 -0
  89. package/dist/utils/logger.d.ts.map +1 -0
  90. package/dist/utils/logger.js +25 -0
  91. package/dist/utils/logger.js.map +1 -0
  92. package/dist/utils/logo.d.ts +2 -0
  93. package/dist/utils/logo.d.ts.map +1 -0
  94. package/dist/utils/logo.js +15 -0
  95. package/dist/utils/logo.js.map +1 -0
  96. package/dist/utils/os-detector.d.ts +10 -0
  97. package/dist/utils/os-detector.d.ts.map +1 -0
  98. package/dist/utils/os-detector.js +76 -0
  99. package/dist/utils/os-detector.js.map +1 -0
  100. package/dist/utils/path-helpers.d.ts +9 -0
  101. package/dist/utils/path-helpers.d.ts.map +1 -0
  102. package/dist/utils/path-helpers.js +96 -0
  103. package/dist/utils/path-helpers.js.map +1 -0
  104. package/dist/utils/port-checker.d.ts +3 -0
  105. package/dist/utils/port-checker.d.ts.map +1 -0
  106. package/dist/utils/port-checker.js +45 -0
  107. package/dist/utils/port-checker.js.map +1 -0
  108. package/dist/utils/project-files.d.ts +7 -0
  109. package/dist/utils/project-files.d.ts.map +1 -0
  110. package/dist/utils/project-files.js +97 -0
  111. package/dist/utils/project-files.js.map +1 -0
  112. package/dist/utils/spawn.d.ts +7 -0
  113. package/dist/utils/spawn.d.ts.map +1 -0
  114. package/dist/utils/spawn.js +31 -0
  115. package/dist/utils/spawn.js.map +1 -0
  116. package/dist/utils/tool-state.d.ts +16 -0
  117. package/dist/utils/tool-state.d.ts.map +1 -0
  118. package/dist/utils/tool-state.js +86 -0
  119. package/dist/utils/tool-state.js.map +1 -0
  120. package/package.json +63 -0
@@ -0,0 +1,641 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.setupPromptTestHooks = void 0;
37
+ exports.runSetup = runSetup;
38
+ exports.createSetupCommand = createSetupCommand;
39
+ const core_1 = require("@clack/core");
40
+ const p = __importStar(require("@clack/prompts"));
41
+ const fs = __importStar(require("fs"));
42
+ const os = __importStar(require("os"));
43
+ const path = __importStar(require("path"));
44
+ const kleur_1 = require("kleur");
45
+ const env_loader_1 = require("../utils/env-loader");
46
+ const project_files_1 = require("../utils/project-files");
47
+ const docker_compose_1 = require("../services/docker-compose");
48
+ const cert_manager_1 = require("../services/cert-manager");
49
+ const domain_checker_1 = require("../services/domain-checker");
50
+ const port_checker_1 = require("../services/port-checker");
51
+ const path_helpers_1 = require("../utils/path-helpers");
52
+ const command_1 = require("../utils/command");
53
+ const logo_1 = require("../utils/logo");
54
+ const tool_state_1 = require("../utils/tool-state");
55
+ const i18n_1 = require("../i18n");
56
+ const STEP_BACK = Symbol('runestone:step-back');
57
+ const BACK_KEY = '\x1b';
58
+ let activeT = (0, i18n_1.createTranslator)((0, i18n_1.resolveLocale)());
59
+ function setActiveLocale(locale) {
60
+ activeT = (0, i18n_1.createTranslator)(locale);
61
+ }
62
+ function cancelIfNeeded(value) {
63
+ if (p.isCancel(value)) {
64
+ p.cancel(activeT('setup.cancelled'));
65
+ process.exit(0);
66
+ }
67
+ return value;
68
+ }
69
+ function isStepBack(value) {
70
+ return value === STEP_BACK;
71
+ }
72
+ function expandHome(value) {
73
+ if (value === '~') {
74
+ return os.homedir();
75
+ }
76
+ if (value.startsWith(`~${path.sep}`) || value.startsWith('~/') || value.startsWith('~\\')) {
77
+ return path.join(os.homedir(), value.slice(2));
78
+ }
79
+ return value;
80
+ }
81
+ function setupEnvPath(projectDir, envPath) {
82
+ return envPath ? path.resolve(expandHome(envPath)) : path.join(projectDir, '.env');
83
+ }
84
+ function childPrompt(message) {
85
+ return `── ${message}`;
86
+ }
87
+ function promptSymbol(state) {
88
+ switch (state) {
89
+ case 'submit':
90
+ return (0, kleur_1.green)('◇');
91
+ case 'cancel':
92
+ return (0, kleur_1.red)('■');
93
+ case 'error':
94
+ return (0, kleur_1.yellow)('▲');
95
+ default:
96
+ return (0, kleur_1.cyan)('◆');
97
+ }
98
+ }
99
+ function optionLabel(option, state) {
100
+ const label = option.label ?? String(option.value);
101
+ if (state === 'active') {
102
+ return `${(0, kleur_1.green)('●')} ${label}${option.hint ? ` ${(0, kleur_1.dim)(`(${option.hint})`)}` : ''}`;
103
+ }
104
+ if (state === 'selected') {
105
+ return (0, kleur_1.dim)(label);
106
+ }
107
+ if (state === 'cancelled') {
108
+ return (0, kleur_1.strikethrough)((0, kleur_1.dim)(label));
109
+ }
110
+ return `${(0, kleur_1.dim)('○')} ${(0, kleur_1.dim)(label)}`;
111
+ }
112
+ function enableBackHotkey(prompt, canBack) {
113
+ if (!canBack) {
114
+ return;
115
+ }
116
+ const promptInternal = prompt;
117
+ const originalOnKeypress = promptInternal.onKeypress?.bind(prompt);
118
+ promptInternal.onKeypress = (input, key) => {
119
+ if (input === BACK_KEY || key?.name === 'escape' || key?.sequence === BACK_KEY) {
120
+ promptInternal.value = STEP_BACK;
121
+ promptInternal.state = 'submit';
122
+ promptInternal.emit?.('finalize');
123
+ promptInternal.render?.();
124
+ promptInternal.close?.();
125
+ return;
126
+ }
127
+ originalOnKeypress?.(input, key);
128
+ };
129
+ }
130
+ exports.setupPromptTestHooks = {
131
+ enableBackHotkey,
132
+ STEP_BACK
133
+ };
134
+ function promptDescription(description) {
135
+ if (!description) {
136
+ return '';
137
+ }
138
+ const lines = Array.isArray(description) ? description : [description];
139
+ return `${lines.map((line) => `${(0, kleur_1.gray)('│')} ${(0, kleur_1.dim)(`- ${line}`)}`).join('\n')}\n`;
140
+ }
141
+ function promptHotkeys(canBack) {
142
+ const hotkeys = [canBack ? activeT('setup.hotkey.back') : undefined, activeT('setup.hotkey.next'), activeT('setup.hotkey.cancel')];
143
+ return hotkeys.filter(Boolean).join((0, kleur_1.dim)(' / '));
144
+ }
145
+ function promptHeader(state, message, frame) {
146
+ return `${(0, kleur_1.gray)('│')}\n${promptSymbol(state)} ${message}\n${promptDescription(frame?.description)}`;
147
+ }
148
+ function promptFooter(canBack, color = kleur_1.cyan) {
149
+ return `${color('│')}\n${color('└')} ${(0, kleur_1.dim)(promptHotkeys(canBack))}\n`;
150
+ }
151
+ function settingsChanged(existing, next) {
152
+ if (!existing?.ENV_FILE_EXISTS) {
153
+ return false;
154
+ }
155
+ return Object.keys(next).some((key) => String(existing[key]) !== String(next[key]));
156
+ }
157
+ function resetConfiguredValues(draft) {
158
+ draft.dockerDomain = undefined;
159
+ draft.projectPrefix = undefined;
160
+ draft.webEntrypointName = undefined;
161
+ draft.webEntrypointPort = undefined;
162
+ draft.webSecureEntrypointName = undefined;
163
+ draft.webSecureEntrypointPort = undefined;
164
+ draft.smtpPort = undefined;
165
+ draft.installMkcert = undefined;
166
+ }
167
+ function setupConfigInput(draft) {
168
+ return {
169
+ HOST_DOMAIN: draft.dockerDomain,
170
+ PREFIX: draft.projectPrefix,
171
+ HTTPS_PORT: draft.webSecureEntrypointPort,
172
+ HTTP_PORT: draft.webEntrypointPort,
173
+ SMTP_PORT: draft.smtpPort,
174
+ WEB_ENTRYPOINT_PORT: draft.webEntrypointPort,
175
+ WEB_SECURE_ENTRYPOINT_PORT: draft.webSecureEntrypointPort,
176
+ RUNESTONE_IMAGE: 'cymondez/runestone',
177
+ RUNESTONE_TAG: '5.2',
178
+ MKCERT_INSTALLED: String(Boolean(draft.installMkcert)),
179
+ WEB_ENTRYPOINT_NAME: draft.webEntrypointName,
180
+ WEB_SECURE_ENTRYPOINT_NAME: draft.webSecureEntrypointName
181
+ };
182
+ }
183
+ function reviewLines(draft) {
184
+ return [
185
+ `${activeT('setup.runestonePath.title')}: ${draft.projectDir}`,
186
+ `${activeT('setup.language.title')}: ${draft.selectedLocale}`,
187
+ `${activeT('setup.dockerDomain.title')}: ${draft.dockerDomain}`,
188
+ `${activeT('setup.projectPrefix.title')}: ${draft.projectPrefix}`,
189
+ `${activeT('setup.httpName.prompt')}: ${draft.webEntrypointName}`,
190
+ `${activeT('setup.httpPort.prompt')}: ${draft.webEntrypointPort}`,
191
+ `${activeT('setup.httpsName.prompt')}: ${draft.webSecureEntrypointName}`,
192
+ `${activeT('setup.httpsPort.prompt')}: ${draft.webSecureEntrypointPort}`,
193
+ `${activeT('setup.mailpit.prompt')}: ${draft.smtpPort}`,
194
+ `${activeT('setup.localCa.prompt')} ${draft.installMkcert ? activeT('common.yes') : activeT('common.no')}`
195
+ ];
196
+ }
197
+ function validatePort(value) {
198
+ const trimmed = value.trim();
199
+ if (!/^\d+$/.test(trimmed)) {
200
+ return activeT('setup.port.number');
201
+ }
202
+ const port = Number(trimmed);
203
+ if (port < 1 || port > 65535) {
204
+ return activeT('setup.port.range');
205
+ }
206
+ return undefined;
207
+ }
208
+ async function promptText(message, initialValue, validate, canBack = false, frame) {
209
+ const prompt = new core_1.TextPrompt({
210
+ initialValue,
211
+ validate,
212
+ render() {
213
+ const header = promptHeader(this.state, message, frame);
214
+ const value = this.value ? this.valueWithCursor : (0, kleur_1.inverse)((0, kleur_1.hidden)('_'));
215
+ switch (this.state) {
216
+ case 'error':
217
+ return `${header}${(0, kleur_1.yellow)('│')} ${value}\n${(0, kleur_1.yellow)('│')} ${(0, kleur_1.yellow)(this.error)}\n${promptFooter(canBack, kleur_1.yellow)}`;
218
+ case 'submit':
219
+ if (isStepBack(this.value)) {
220
+ return `${header}${(0, kleur_1.gray)('│')} ${(0, kleur_1.dim)(activeT('setup.backSubmitted'))}`;
221
+ }
222
+ return `${header}${(0, kleur_1.gray)('│')} ${(0, kleur_1.dim)(this.value || initialValue)}`;
223
+ case 'cancel':
224
+ return `${header}${(0, kleur_1.gray)('│')} ${(0, kleur_1.strikethrough)((0, kleur_1.dim)(this.value ?? ''))}`;
225
+ default:
226
+ return `${header}${(0, kleur_1.cyan)('│')} ${value}\n${promptFooter(canBack)}`;
227
+ }
228
+ }
229
+ });
230
+ enableBackHotkey(prompt, canBack);
231
+ const result = cancelIfNeeded(await prompt.prompt());
232
+ return isStepBack(result) ? STEP_BACK : result.trim();
233
+ }
234
+ async function promptConfirm(options) {
235
+ const active = options.active ?? activeT('common.yes');
236
+ const inactive = options.inactive ?? activeT('common.no');
237
+ const canBack = options.canBack ?? false;
238
+ const prompt = new core_1.ConfirmPrompt({
239
+ active,
240
+ inactive,
241
+ initialValue: options.initialValue,
242
+ render() {
243
+ const header = promptHeader(this.state, options.message, options.frame);
244
+ const value = this.value ? active : inactive;
245
+ switch (this.state) {
246
+ case 'submit':
247
+ if (isStepBack(this.value)) {
248
+ return `${header}${(0, kleur_1.gray)('│')} ${(0, kleur_1.dim)(activeT('setup.backSubmitted'))}`;
249
+ }
250
+ return `${header}${(0, kleur_1.gray)('│')} ${(0, kleur_1.dim)(value)}`;
251
+ case 'cancel':
252
+ return `${header}${(0, kleur_1.gray)('│')} ${(0, kleur_1.strikethrough)((0, kleur_1.dim)(value))}\n${(0, kleur_1.gray)('│')}`;
253
+ default:
254
+ return `${header}${(0, kleur_1.cyan)('│')} ${this.value ? `${(0, kleur_1.green)('●')} ${active}` : `${(0, kleur_1.dim)('○')} ${(0, kleur_1.dim)(active)}`} ${(0, kleur_1.dim)('/')} ${this.value ? `${(0, kleur_1.dim)('○')} ${(0, kleur_1.dim)(inactive)}` : `${(0, kleur_1.green)('●')} ${inactive}`}\n${promptFooter(canBack)}`;
255
+ }
256
+ }
257
+ });
258
+ enableBackHotkey(prompt, canBack);
259
+ return cancelIfNeeded((await prompt.prompt()));
260
+ }
261
+ async function promptSelect(options) {
262
+ const canBack = options.canBack ?? false;
263
+ const prompt = new core_1.SelectPrompt({
264
+ options: options.options,
265
+ initialValue: options.initialValue,
266
+ render() {
267
+ const header = promptHeader(this.state, options.message, options.frame);
268
+ switch (this.state) {
269
+ case 'submit':
270
+ if (isStepBack(this.value)) {
271
+ return `${header}${(0, kleur_1.gray)('│')} ${(0, kleur_1.dim)(activeT('setup.backSubmitted'))}`;
272
+ }
273
+ return `${header}${(0, kleur_1.gray)('│')} ${optionLabel(this.options[this.cursor], 'selected')}`;
274
+ case 'cancel':
275
+ return `${header}${(0, kleur_1.gray)('│')} ${optionLabel(this.options[this.cursor], 'cancelled')}\n${(0, kleur_1.gray)('│')}`;
276
+ default:
277
+ return `${header}${(0, kleur_1.cyan)('│')} ${this.options
278
+ .map((option, index) => optionLabel(option, index === this.cursor ? 'active' : 'inactive'))
279
+ .join(`\n${(0, kleur_1.cyan)('│')} `)}\n${promptFooter(canBack)}`;
280
+ }
281
+ }
282
+ });
283
+ enableBackHotkey(prompt, canBack);
284
+ return cancelIfNeeded((await prompt.prompt()));
285
+ }
286
+ async function promptReview(draft) {
287
+ const result = await promptSelect({
288
+ message: activeT('setup.review.prompt'),
289
+ options: [
290
+ { value: 'apply', label: activeT('setup.review.apply') },
291
+ { value: 'back', label: activeT('setup.review.back') },
292
+ { value: 'cancel', label: activeT('setup.review.cancel') }
293
+ ],
294
+ initialValue: 'apply',
295
+ canBack: true,
296
+ frame: {
297
+ description: reviewLines(draft)
298
+ }
299
+ });
300
+ return isStepBack(result) ? 'back' : result;
301
+ }
302
+ async function promptPort(options) {
303
+ while (true) {
304
+ const value = await promptText(options.message, options.initialValue, validatePort, options.canBack, options.frame);
305
+ if (isStepBack(value)) {
306
+ return STEP_BACK;
307
+ }
308
+ if (value === options.allowOccupiedValue) {
309
+ return value;
310
+ }
311
+ const available = await (0, port_checker_1.isPortAvailable)(Number(value));
312
+ if (available) {
313
+ return value;
314
+ }
315
+ p.log.warn(activeT('setup.port.inUse', { port: value }));
316
+ }
317
+ }
318
+ function logDomainCheckResult(result) {
319
+ p.log.info([
320
+ activeT('setup.domainCheck.localAddresses', { domain: result.domain }),
321
+ ...result.localAddresses.map((address) => ` - ${address}`),
322
+ activeT('setup.domainCheck.externalIpv6', { value: result.hasExternalIpv6 ? 'yes' : 'no' })
323
+ ].join('\n'));
324
+ for (const host of result.checkedHosts) {
325
+ const lines = [
326
+ activeT('setup.domainCheck.resolvedTo', { host: host.host }),
327
+ ...(host.resolved.length > 0
328
+ ? host.resolved.map((record) => ` - ${record.address}`)
329
+ : [` - ${activeT('setup.domainCheck.noRecords')}`])
330
+ ];
331
+ p.log.info(lines.join('\n'));
332
+ for (const warning of host.warnings) {
333
+ p.log.warn(warning);
334
+ }
335
+ for (const error of host.errors) {
336
+ p.log.error(error);
337
+ }
338
+ }
339
+ }
340
+ async function maybeCheckDockerDomain(domain, canBack) {
341
+ if (domain === env_loader_1.DEFAULT_ENV.HOST_DOMAIN) {
342
+ return 'next';
343
+ }
344
+ const shouldCheck = await promptConfirm({
345
+ message: activeT('setup.domainCheck.prompt'),
346
+ active: activeT('common.yes'),
347
+ inactive: activeT('common.no'),
348
+ initialValue: true,
349
+ canBack,
350
+ frame: {
351
+ description: [
352
+ activeT('setup.domainCheck.description1', { domain }),
353
+ activeT('setup.domainCheck.description2', { domain }),
354
+ activeT('setup.domainCheck.description3')
355
+ ]
356
+ }
357
+ });
358
+ if (isStepBack(shouldCheck)) {
359
+ return 'back';
360
+ }
361
+ if (!shouldCheck) {
362
+ p.log.warn(activeT('setup.domainCheck.skipped'));
363
+ return 'next';
364
+ }
365
+ const spinner = p.spinner();
366
+ spinner.start(activeT('setup.domainCheck.spinner'));
367
+ const result = await (0, domain_checker_1.checkDomainResolvesToThisMachine)(domain);
368
+ spinner.stop(activeT('setup.domainCheck.spinnerDone', { status: result.status }));
369
+ logDomainCheckResult(result);
370
+ if (result.status === 'pass') {
371
+ p.log.success(activeT('setup.domainCheck.success'));
372
+ return 'next';
373
+ }
374
+ const continueAnyway = await promptConfirm({
375
+ message: result.status === 'warn'
376
+ ? activeT('setup.domainCheck.warnContinue')
377
+ : activeT('setup.domainCheck.failContinue'),
378
+ active: activeT('common.yes'),
379
+ inactive: activeT('common.no'),
380
+ initialValue: result.status === 'warn',
381
+ canBack
382
+ });
383
+ if (isStepBack(continueAnyway)) {
384
+ return 'back';
385
+ }
386
+ if (!continueAnyway) {
387
+ p.cancel(activeT('setup.domainCheck.cancelled'));
388
+ process.exit(1);
389
+ }
390
+ return 'next';
391
+ }
392
+ async function createDefaultCertificates(projectDir, domain) {
393
+ const certsDir = path.join(projectDir, 'certs');
394
+ path_helpers_1.pathHelpers.ensureDir(certsDir);
395
+ await (0, cert_manager_1.ensureWildcardCertificate)(projectDir, domain, { installLocalCa: true });
396
+ await (0, cert_manager_1.ensureWildcardCertificate)(projectDir, 'traefik.me', { installLocalCa: true });
397
+ const aliasResult = (0, cert_manager_1.ensureRootCaAliases)(certsDir);
398
+ for (const warning of aliasResult.warnings) {
399
+ p.log.warn(warning);
400
+ }
401
+ }
402
+ async function runSetup(options = {}) {
403
+ p.intro((0, logo_1.loadRunestoneLogo)());
404
+ const initialProjectDir = path_helpers_1.pathHelpers.resolveProjectDir(options.project);
405
+ const initialEnvPath = setupEnvPath(initialProjectDir, options.envPath);
406
+ const initialExistingConfig = fs.existsSync(initialEnvPath) ? env_loader_1.envLoader.load(initialEnvPath, initialProjectDir) : undefined;
407
+ const draft = {
408
+ projectDir: initialProjectDir,
409
+ envPath: initialEnvPath,
410
+ existingConfig: initialExistingConfig,
411
+ selectedLocale: (0, i18n_1.initialSetupLocale)(tool_state_1.toolState.readLocale())
412
+ };
413
+ setActiveLocale(draft.selectedLocale);
414
+ let announcedEnvPath;
415
+ const announceExistingSettings = () => {
416
+ if (draft.existingConfig?.ENV_FILE_EXISTS && announcedEnvPath !== draft.envPath) {
417
+ p.log.info(activeT('setup.existingSettings', { envPath: draft.envPath }));
418
+ announcedEnvPath = draft.envPath;
419
+ }
420
+ };
421
+ const steps = [
422
+ async (canBack) => {
423
+ const selectedLocale = await promptSelect({
424
+ message: activeT('setup.language.prompt'),
425
+ options: (0, i18n_1.languageChoices)(),
426
+ initialValue: draft.selectedLocale,
427
+ canBack,
428
+ frame: { description: activeT('setup.language.description') }
429
+ });
430
+ if (isStepBack(selectedLocale)) {
431
+ return 'back';
432
+ }
433
+ draft.selectedLocale = selectedLocale;
434
+ setActiveLocale(draft.selectedLocale);
435
+ return 'next';
436
+ }
437
+ ];
438
+ if (!options.project && !options.envPath && !initialExistingConfig?.ENV_FILE_EXISTS) {
439
+ steps.push(async (canBack) => {
440
+ const selectedPath = await promptText(activeT('setup.runestonePath.title'), draft.projectDir, (value) => (!value.trim() ? activeT('setup.runestonePath.required') : undefined), canBack, { description: activeT('setup.runestonePath.description') });
441
+ if (isStepBack(selectedPath)) {
442
+ return 'back';
443
+ }
444
+ const nextProjectDir = path.resolve(expandHome(selectedPath));
445
+ if (nextProjectDir !== draft.projectDir) {
446
+ resetConfiguredValues(draft);
447
+ announcedEnvPath = undefined;
448
+ }
449
+ draft.projectDir = nextProjectDir;
450
+ draft.envPath = setupEnvPath(draft.projectDir, options.envPath);
451
+ draft.existingConfig = fs.existsSync(draft.envPath) ? env_loader_1.envLoader.load(draft.envPath, draft.projectDir) : undefined;
452
+ return 'next';
453
+ });
454
+ }
455
+ steps.push(async (canBack) => {
456
+ announceExistingSettings();
457
+ while (true) {
458
+ const dockerDomain = await promptText(activeT('setup.dockerDomain.title'), draft.dockerDomain ?? draft.existingConfig?.HOST_DOMAIN ?? env_loader_1.DEFAULT_ENV.HOST_DOMAIN, (value) => (!value.trim() ? activeT('setup.dockerDomain.required') : undefined), canBack, { description: activeT('setup.dockerDomain.description') });
459
+ if (isStepBack(dockerDomain)) {
460
+ return 'back';
461
+ }
462
+ draft.dockerDomain = dockerDomain;
463
+ const domainCheckResult = await maybeCheckDockerDomain(draft.dockerDomain, true);
464
+ if (domainCheckResult === 'back') {
465
+ continue;
466
+ }
467
+ return 'next';
468
+ }
469
+ }, async (canBack) => {
470
+ const projectPrefix = await promptText(activeT('setup.projectPrefix.title'), draft.projectPrefix ?? draft.existingConfig?.PREFIX ?? env_loader_1.DEFAULT_ENV.PREFIX, (value) => (!value.trim() ? activeT('setup.projectPrefix.required') : undefined), canBack, { description: activeT('setup.projectPrefix.description') });
471
+ if (isStepBack(projectPrefix)) {
472
+ return 'back';
473
+ }
474
+ draft.projectPrefix = projectPrefix;
475
+ return 'next';
476
+ }, async (canBack) => {
477
+ const webEntrypointName = await promptText(childPrompt(activeT('setup.httpName.prompt')), draft.webEntrypointName ?? draft.existingConfig?.WEB_ENTRYPOINT_NAME ?? env_loader_1.DEFAULT_ENV.WEB_ENTRYPOINT_NAME, (value) => (!value.trim() ? activeT('setup.httpName.required') : undefined), canBack, {
478
+ description: [
479
+ activeT('setup.httpGroup.title'),
480
+ activeT('setup.httpGroup.description1'),
481
+ activeT('setup.httpGroup.description2')
482
+ ]
483
+ });
484
+ if (isStepBack(webEntrypointName)) {
485
+ return 'back';
486
+ }
487
+ draft.webEntrypointName = webEntrypointName;
488
+ return 'next';
489
+ }, async (canBack) => {
490
+ const webEntrypointPort = await promptPort({
491
+ initialValue: draft.webEntrypointPort ?? draft.existingConfig?.WEB_ENTRYPOINT_PORT ?? env_loader_1.DEFAULT_ENV.WEB_ENTRYPOINT_PORT,
492
+ allowOccupiedValue: draft.existingConfig?.WEB_ENTRYPOINT_PORT,
493
+ message: childPrompt(activeT('setup.httpPort.prompt')),
494
+ canBack,
495
+ frame: {
496
+ description: [
497
+ activeT('setup.httpGroup.title'),
498
+ activeT('setup.httpGroup.description1'),
499
+ activeT('setup.httpGroup.description2')
500
+ ]
501
+ }
502
+ });
503
+ if (isStepBack(webEntrypointPort)) {
504
+ return 'back';
505
+ }
506
+ draft.webEntrypointPort = webEntrypointPort;
507
+ return 'next';
508
+ }, async (canBack) => {
509
+ const webSecureEntrypointName = await promptText(childPrompt(activeT('setup.httpsName.prompt')), draft.webSecureEntrypointName ?? draft.existingConfig?.WEB_SECURE_ENTRYPOINT_NAME ?? env_loader_1.DEFAULT_ENV.WEB_SECURE_ENTRYPOINT_NAME, (value) => (!value.trim() ? activeT('setup.httpsName.required') : undefined), canBack, {
510
+ description: [
511
+ activeT('setup.httpsGroup.title'),
512
+ activeT('setup.httpsGroup.description1'),
513
+ activeT('setup.httpsGroup.description2')
514
+ ]
515
+ });
516
+ if (isStepBack(webSecureEntrypointName)) {
517
+ return 'back';
518
+ }
519
+ draft.webSecureEntrypointName = webSecureEntrypointName;
520
+ return 'next';
521
+ }, async (canBack) => {
522
+ const webSecureEntrypointPort = await promptPort({
523
+ initialValue: draft.webSecureEntrypointPort ??
524
+ draft.existingConfig?.WEB_SECURE_ENTRYPOINT_PORT ??
525
+ env_loader_1.DEFAULT_ENV.WEB_SECURE_ENTRYPOINT_PORT,
526
+ allowOccupiedValue: draft.existingConfig?.WEB_SECURE_ENTRYPOINT_PORT,
527
+ message: childPrompt(activeT('setup.httpsPort.prompt')),
528
+ canBack,
529
+ frame: {
530
+ description: [
531
+ activeT('setup.httpsGroup.title'),
532
+ activeT('setup.httpsGroup.description1'),
533
+ activeT('setup.httpsGroup.description2')
534
+ ]
535
+ }
536
+ });
537
+ if (isStepBack(webSecureEntrypointPort)) {
538
+ return 'back';
539
+ }
540
+ draft.webSecureEntrypointPort = webSecureEntrypointPort;
541
+ return 'next';
542
+ }, async (canBack) => {
543
+ const smtpPort = await promptPort({
544
+ initialValue: draft.smtpPort ?? draft.existingConfig?.SMTP_PORT ?? env_loader_1.DEFAULT_ENV.SMTP_PORT,
545
+ allowOccupiedValue: draft.existingConfig?.SMTP_PORT,
546
+ message: childPrompt(activeT('setup.mailpit.prompt')),
547
+ canBack,
548
+ frame: { description: activeT('setup.mailpit.description') }
549
+ });
550
+ if (isStepBack(smtpPort)) {
551
+ return 'back';
552
+ }
553
+ draft.smtpPort = smtpPort;
554
+ return 'next';
555
+ }, async (canBack) => {
556
+ const installMkcert = await promptConfirm({
557
+ message: activeT('setup.localCa.prompt'),
558
+ active: activeT('common.yes'),
559
+ inactive: activeT('common.no'),
560
+ initialValue: draft.installMkcert ??
561
+ (draft.existingConfig ? draft.existingConfig.MKCERT_INSTALLED === 'true' : true),
562
+ canBack,
563
+ frame: { description: activeT('setup.localCa.description') }
564
+ });
565
+ if (isStepBack(installMkcert)) {
566
+ return 'back';
567
+ }
568
+ draft.installMkcert = installMkcert;
569
+ return 'next';
570
+ });
571
+ let stepIndex = 0;
572
+ while (true) {
573
+ while (stepIndex < steps.length) {
574
+ const stepResult = await steps[stepIndex](stepIndex > 0);
575
+ stepIndex += stepResult === 'back' ? -1 : 1;
576
+ }
577
+ const reviewAction = await promptReview(draft);
578
+ if (reviewAction === 'apply') {
579
+ break;
580
+ }
581
+ if (reviewAction === 'cancel') {
582
+ p.cancel(activeT('setup.cancelled'));
583
+ process.exit(0);
584
+ }
585
+ stepIndex = Math.max(steps.length - 1, 0);
586
+ }
587
+ const configToWrite = setupConfigInput(draft);
588
+ const spinner = p.spinner();
589
+ spinner.start(activeT('setup.writingFiles'));
590
+ env_loader_1.envLoader.write(draft.envPath, configToWrite);
591
+ const config = env_loader_1.envLoader.load(draft.envPath, draft.projectDir);
592
+ (0, project_files_1.ensureProjectFiles)(config, { overwriteCompose: true });
593
+ tool_state_1.toolState.writeSetupState({ runestonePath: draft.projectDir, locale: draft.selectedLocale });
594
+ spinner.stop(activeT('setup.configurationWritten', { envPath: draft.envPath }));
595
+ if (draft.installMkcert) {
596
+ const caSpinner = p.spinner();
597
+ caSpinner.start(activeT('setup.localCa.spinner'));
598
+ await createDefaultCertificates(config.PROJECT_DIR, config.HOST_DOMAIN);
599
+ caSpinner.stop(activeT('setup.localCa.done'));
600
+ }
601
+ if (settingsChanged(draft.existingConfig, configToWrite)) {
602
+ const restart = cancelIfNeeded(await p.confirm({
603
+ message: activeT('setup.restart.prompt'),
604
+ active: activeT('common.yes'),
605
+ inactive: activeT('common.no'),
606
+ initialValue: true
607
+ }));
608
+ if (restart) {
609
+ const running = docker_compose_1.composeService.ps(config.COMPOSE_FILE_PATH).some((container) => container.State === 'running');
610
+ if (running) {
611
+ const restartSpinner = p.spinner();
612
+ restartSpinner.start(activeT('setup.restart.spinner'));
613
+ docker_compose_1.composeService.restart(config.COMPOSE_FILE_PATH);
614
+ restartSpinner.stop(activeT('setup.restart.done'));
615
+ }
616
+ else {
617
+ p.log.warn(activeT('setup.restart.notRunning'));
618
+ }
619
+ }
620
+ }
621
+ p.log.success(activeT('setup.result.path', { path: config.PROJECT_DIR }));
622
+ p.log.success(activeT('setup.result.compose', { path: config.COMPOSE_FILE_PATH }));
623
+ p.log.success(activeT('setup.result.domain', { domain: config.HOST_DOMAIN }));
624
+ p.outro(activeT('setup.outro'));
625
+ return env_loader_1.envLoader.load(draft.envPath, draft.projectDir);
626
+ }
627
+ function createSetupCommand() {
628
+ return (0, command_1.createCommand)('setup')
629
+ .description((0, i18n_1.t)('commands.setup.description'))
630
+ .action(async () => {
631
+ try {
632
+ await runSetup();
633
+ }
634
+ catch (error) {
635
+ const message = error instanceof Error ? error.message : String(error);
636
+ p.cancel(activeT('setup.failed', { message }));
637
+ process.exit(1);
638
+ }
639
+ });
640
+ }
641
+ //# sourceMappingURL=setup.js.map