hub-launch 1.25.0 → 1.27.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.
@@ -129,200 +129,62 @@ export function initCommand(program, _config) {
129
129
  }
130
130
  });
131
131
  }
132
- /**
133
- * Get the deployment startup script template based on auth provider
134
- */
135
- function getDeploymentStartupTemplate(authProvider) {
136
- const header = `#!/usr/bin/env tsx
137
- /**
138
- * Deployment Startup Script
139
- * Automatically logs in to preview deployment before opening
140
- *
141
- * Context passed as first argument (JSON string):
142
- * {
143
- * deploymentUrl: string;
144
- * issueNumber?: number;
145
- * prNumber?: number;
146
- * prTitle?: string;
147
- * prUrl?: string;
148
- * }
149
- */
150
-
151
- import { chromium } from "@playwright/test";
152
-
153
- interface HookContext {
154
- deploymentUrl: string;
155
- issueNumber?: number;
156
- prNumber?: number;
157
- }
158
-
159
- async function main() {
160
- const contextJson = process.argv[2];
161
- if (!contextJson) {
162
- console.error("Error: No context provided");
163
- process.exit(1);
164
- }
165
-
166
- const context: HookContext = JSON.parse(contextJson);
167
- console.log(\`🚀 Starting preview for: \${context.deploymentUrl}\`);
168
-
169
- // Browser headless mode: Use env var or default to visible
170
- const headless = process.env.CI === "true" || process.env.HEADLESS === "true";
171
-
172
- const browser = await chromium.launch({
173
- headless,
174
- slowMo: headless ? 0 : 100,
175
- });
176
-
177
- const page = await browser.newPage();
178
-
179
- try {
180
- await page.goto(context.deploymentUrl);
181
- `;
182
- if (authProvider === 'clerk') {
183
- return (header +
184
- `
185
- // Wait for Clerk authentication widget
186
- await page.waitForSelector('[data-clerk-id]', { timeout: 5000 });
187
-
188
- // Click sign-in button
189
- await page.click('button:has-text("Sign in")');
190
-
191
- // Fill in credentials from environment
192
- const email = process.env.TEST_USER_EMAIL;
193
- const password = process.env.TEST_USER_PASSWORD;
194
-
195
- if (!email || !password) {
196
- throw new Error(
197
- "Required: TEST_USER_EMAIL and TEST_USER_PASSWORD environment variables"
198
- );
199
- }
200
-
201
- // Enter email
202
- await page.fill('input[name="identifier"]', email);
203
- await page.click('button:has-text("Continue")');
204
-
205
- // Enter password
206
- await page.fill('input[name="password"]', password);
207
- await page.click('button:has-text("Continue")');
208
-
209
- // Wait for successful login
210
- await page.waitForURL(\`\${context.deploymentUrl}/**\`, {
211
- timeout: 10000
212
- });
213
-
214
- console.log("✅ Successfully logged in with Clerk!");
215
- console.log("Browser will remain open for testing.");
216
-
217
- // Keep browser open - user closes when done
218
- // Don't call browser.close()
219
-
220
- } catch (error) {
221
- console.error("❌ Login failed:", error);
222
- await browser.close();
223
- process.exit(1);
224
- }
225
- }
226
-
227
- main().catch(console.error);
228
- `);
229
- }
230
- if (authProvider === 'auth0') {
231
- return (header +
232
- `
233
- // Wait for Auth0 login form
234
- await page.waitForSelector('[name="username"]', { timeout: 5000 });
235
-
236
- // Fill in credentials from environment
237
- const email = process.env.TEST_USER_EMAIL;
238
- const password = process.env.TEST_USER_PASSWORD;
239
-
240
- if (!email || !password) {
241
- throw new Error(
242
- "Required: TEST_USER_EMAIL and TEST_USER_PASSWORD environment variables"
243
- );
244
- }
245
-
246
- // Auth0 login flow
247
- await page.fill('input[name="username"]', email);
248
- await page.fill('input[name="password"]', password);
249
- await page.click('button[type="submit"]');
132
+ const HOOKS_README_TEMPLATE = `# HubLaunch Hooks
250
133
 
251
- // Wait for redirect back to app
252
- await page.waitForURL(\`\${context.deploymentUrl}/**\`, {
253
- timeout: 10000
254
- });
134
+ This directory contains project-specific hooks that extend HubLaunch functionality.
255
135
 
256
- console.log("✅ Successfully logged in with Auth0!");
257
- console.log("Browser will remain open for testing.");
136
+ \`beforeLaunch.ts\` and \`afterMerge.ts\` are generated with a working local-only example already active, plus commented-out Slack and database-branch (Neon+Vercel) examples you can uncomment and adapt.
258
137
 
259
- } catch (error) {
260
- console.error("❌ Login failed:", error);
261
- await browser.close();
262
- process.exit(1);
263
- }
264
- }
138
+ ## Available Hooks
265
139
 
266
- main().catch(console.error);
267
- `);
268
- }
269
- // Generic template (no auth or custom)
270
- return (header +
271
- `
272
- // Add your custom preview initialization logic here
273
- //
274
- // Examples:
275
- // - Authenticate to deployment
276
- // - Seed test data via API
277
- // - Configure browser state
278
- // - Navigate to specific page
279
-
280
- console.log("✅ Preview setup complete!");
281
- console.log("Browser opened at:", context.deploymentUrl);
282
-
283
- // Optionally close browser after delay
284
- // await new Promise(resolve => setTimeout(resolve, 30000));
285
- // await browser.close();
286
-
287
- } catch (error) {
288
- console.error("❌ Preview setup failed:", error);
289
- await browser.close();
290
- process.exit(1);
291
- }
292
- }
140
+ ### \`beforeLaunch.ts\`
141
+ Runs **before** \`hula launch\` sends its launch request, so a project can
142
+ provision a freshly-created, per-launch resource (e.g. a database branch) and
143
+ inject its connection string into the container.
144
+
145
+ - On success it must print \`{"envVars": {"KEY": "value", ...}}\` to **stdout**.
146
+ Those values are merged into the launch request's \`envVars\`, taking
147
+ precedence over any \`.env\`-sourced values with the same key.
148
+ - **A nonzero exit, or stdout that is not valid \`{"envVars": {...}}\` JSON,
149
+ aborts the launch** — no request is sent. (Launching against the wrong
150
+ resource, e.g. a shared database, is worse than not launching.)
151
+ - **stderr streams live** to your terminal, so you can watch the hook's
152
+ progress (it may make several seconds of API calls).
153
+ - Printing nothing, \`{}\`, or \`{"envVars": {}}\` is a valid no-op — the launch
154
+ proceeds with no injected variables.
155
+ - The hook is responsible for its own **find-or-create idempotency**: a
156
+ kill-and-relaunch runs \`beforeLaunch\` again with the same context, so key
157
+ your resource off \`issueName\`/\`planPath\` and reuse an existing resource
158
+ rather than creating a duplicate.
293
159
 
294
- main().catch(console.error);
295
- `);
160
+ **Context Provided**:
161
+ \`\`\`typescript
162
+ {
163
+ issueName: string; // The launch's issue/plan name
164
+ planPath: string; // Path to the plan being launched
296
165
  }
297
- const HOOKS_README_TEMPLATE = `# HubLaunch Hooks
166
+ \`\`\`
298
167
 
299
- This directory contains project-specific hooks that extend HubLaunch functionality.
168
+ ---
300
169
 
301
- ## Available Hooks
170
+ ### \`afterMerge.ts\`
171
+ Runs **after** \`hula approve\` successfully merges a PR. Use it to clean up
172
+ whatever a \`beforeLaunch\` hook provisioned (e.g. delete the per-launch
173
+ database branch).
302
174
 
303
- ### \`deploymentStartupScript.ts\`
304
- Runs before opening a preview deployment. Use for:
305
- - **Authentication** (Clerk, Auth0, custom login flows)
306
- - **Browser automation** (Playwright-based interactions)
307
- - **Test data seeding** (API calls to populate data)
308
- - **Environment setup** (configure local state)
175
+ - **Best-effort**: a failure only logs a warning — it never fails the merge,
176
+ which has already completed by the time this hook runs.
309
177
 
310
178
  **Context Provided**:
311
179
  \`\`\`typescript
312
180
  {
313
- deploymentUrl: string; // Preview deployment URL
314
- issueNumber?: number; // Associated issue number
315
- prNumber?: number; // Associated PR number
316
- prTitle?: string; // PR title
317
- prUrl?: string; // PR URL
181
+ issueNumber: number; // Associated issue number
182
+ prNumber: number; // Merged PR number
183
+ prTitle: string; // Merged PR title
184
+ branch: string; // The merged PR's source branch
318
185
  }
319
186
  \`\`\`
320
187
 
321
- **Usage**:
322
- - Configured in \`.hublaunch/hublaunch.config.ts\`
323
- - Runs automatically when you run \`hula preview\`
324
- - Runs in **background** (non-blocking)
325
-
326
188
  ---
327
189
 
328
190
  ## Creating Custom Hooks
@@ -372,11 +234,8 @@ export const config = {
372
234
  // ... other config ...
373
235
 
374
236
  hooks: {
375
- deploymentStartup: ".hublaunch/hooks/deploymentStartupScript.ts",
376
- beforePreview: ".hublaunch/hooks/beforePreview.ts", // optional
377
- afterPreview: ".hublaunch/hooks/afterPreview.ts", // optional
378
- beforeMerge: ".hublaunch/hooks/beforeMerge.ts", // optional
379
- afterMerge: ".hublaunch/hooks/afterMerge.ts", // optional
237
+ beforeLaunch: ".hublaunch/hooks/beforeLaunch.ts", // optional — inject per-launch env vars
238
+ afterMerge: ".hublaunch/hooks/afterMerge.ts", // optional — cleanup after merge
380
239
  },
381
240
  };
382
241
  \`\`\`
@@ -409,10 +268,10 @@ Run hook manually for testing:
409
268
 
410
269
  \`\`\`bash
411
270
  # Test with sample context
412
- tsx .hublaunch/hooks/deploymentStartupScript.ts '{"deploymentUrl":"https://example.com","prNumber":123}'
271
+ tsx .hublaunch/hooks/beforeLaunch.ts '{"issueName":"my-feature","planPath":".hublaunch/plans/my-feature.md"}'
413
272
 
414
273
  # Enable debug mode
415
- DEBUG=true tsx .hublaunch/hooks/deploymentStartupScript.ts '{"deploymentUrl":"https://example.com"}'
274
+ DEBUG=true tsx .hublaunch/hooks/beforeLaunch.ts '{"issueName":"my-feature","planPath":".hublaunch/plans/my-feature.md"}'
416
275
  \`\`\`
417
276
 
418
277
  ---
@@ -423,6 +282,182 @@ DEBUG=true tsx .hublaunch/hooks/deploymentStartupScript.ts '{"deploymentUrl":"ht
423
282
  - **Examples**: https://github.com/YizYah/hub-launch/tree/main/examples
424
283
  - **Support**: https://github.com/YizYah/hub-launch/issues
425
284
  `;
285
+ const BEFORE_LAUNCH_HOOK_TEMPLATE = `#!/usr/bin/env tsx
286
+ /**
287
+ * beforeLaunch hook — runs before \`hula launch\` sends its launch request.
288
+ * See ./README.md for the full stdout/stderr contract.
289
+ *
290
+ * Context: { issueName: string; planPath: string }
291
+ */
292
+
293
+ interface BeforeLaunchContext {
294
+ issueName: string;
295
+ planPath: string;
296
+ }
297
+
298
+ async function main() {
299
+ const context: BeforeLaunchContext = JSON.parse(process.argv[2] ?? '{}');
300
+
301
+ // -----------------------------------------------------------------------
302
+ // ACTIVE EXAMPLE — runs by default. Local-only, no external services or
303
+ // credentials required. Demonstrates the {"envVars": {...}} stdout
304
+ // contract by injecting a LAUNCH_TIMESTAMP env var into the container.
305
+ // Remove this block once you add your own logic below.
306
+ // -----------------------------------------------------------------------
307
+ console.error(\`[beforeLaunch] Launching "\${context.issueName}"...\`);
308
+ const envVars: Record<string, string> = { LAUNCH_TIMESTAMP: new Date().toISOString() };
309
+ console.error(\`[beforeLaunch] Injecting LAUNCH_TIMESTAMP=\${envVars.LAUNCH_TIMESTAMP}\`);
310
+
311
+ // -----------------------------------------------------------------------
312
+ // EXAMPLE IDEA — Slack notification. Uncomment and set SLACK_WEBHOOK_URL
313
+ // to post a message when a launch starts.
314
+ // -----------------------------------------------------------------------
315
+ // const webhookUrl = process.env.SLACK_WEBHOOK_URL;
316
+ // if (webhookUrl) {
317
+ // await fetch(webhookUrl, {
318
+ // method: 'POST',
319
+ // headers: { 'Content-Type': 'application/json' },
320
+ // body: JSON.stringify({ text: \`🚀 Launching *\${context.issueName}*\` }),
321
+ // });
322
+ // console.error('[beforeLaunch] Slack notification sent.');
323
+ // }
324
+
325
+ // -----------------------------------------------------------------------
326
+ // EXAMPLE IDEA — per-launch database branch (Neon + Vercel). Provisions
327
+ // an isolated Postgres branch and injects its connection string, so each
328
+ // launch gets its own database instead of sharing one. Requires
329
+ // NEON_API_KEY, NEON_PROJECT_ID, VERCEL_TOKEN, VERCEL_PROJECT_ID.
330
+ // Find-or-create by name is required (this hook re-runs on a
331
+ // kill-and-relaunch with the same issueName) — never log the actual
332
+ // connection string or tokens.
333
+ // -----------------------------------------------------------------------
334
+ // const branchName = \`preview/\${context.issueName}\`;
335
+ // const branches = await fetch(
336
+ // \`https://console.neon.tech/api/v2/projects/\${process.env.NEON_PROJECT_ID}/branches\`,
337
+ // { headers: { Authorization: \`Bearer \${process.env.NEON_API_KEY}\` } },
338
+ // ).then((r) => r.json());
339
+ // // ...find branchName in branches.branches; if absent, POST to create one
340
+ // // off the default branch, then read the connection string back.
341
+ // // let connectionString = /* from the found or created branch */ '';
342
+ // await fetch(
343
+ // \`https://api.vercel.com/v10/projects/\${process.env.VERCEL_PROJECT_ID}/env\`,
344
+ // {
345
+ // method: 'POST',
346
+ // headers: {
347
+ // Authorization: \`Bearer \${process.env.VERCEL_TOKEN}\`,
348
+ // 'Content-Type': 'application/json',
349
+ // },
350
+ // body: JSON.stringify({
351
+ // key: 'DATABASE_URL',
352
+ // value: /* connectionString from above */ '',
353
+ // target: ['preview'],
354
+ // gitBranch: context.issueName,
355
+ // type: 'encrypted',
356
+ // }),
357
+ // },
358
+ // );
359
+ // envVars.DATABASE_URL = /* connectionString from above */ '';
360
+ // console.error('[beforeLaunch] Neon branch + Vercel env var provisioned.');
361
+
362
+ console.log(JSON.stringify({ envVars }));
363
+ }
364
+
365
+ main().catch((error) => {
366
+ console.error('[beforeLaunch] Hook failed:', error instanceof Error ? error.message : String(error));
367
+ process.exit(1);
368
+ });
369
+ `;
370
+ const AFTER_MERGE_HOOK_TEMPLATE = `#!/usr/bin/env tsx
371
+ /**
372
+ * afterMerge hook — runs after \`hula approve\` successfully merges a PR.
373
+ * Best-effort: a failure only logs a warning, it never fails the merge.
374
+ * See ./README.md for the full contract.
375
+ *
376
+ * Context: { issueNumber: number; prNumber: number; prTitle: string; branch: string }
377
+ */
378
+
379
+ interface AfterMergeContext {
380
+ issueNumber: number;
381
+ prNumber: number;
382
+ prTitle: string;
383
+ branch: string;
384
+ }
385
+
386
+ async function main() {
387
+ const context: AfterMergeContext = JSON.parse(process.argv[2] ?? '{}');
388
+
389
+ // -----------------------------------------------------------------------
390
+ // ACTIVE EXAMPLE — runs by default. Local-only: just logs what merged.
391
+ // Remove this block once you add your own cleanup logic below.
392
+ // -----------------------------------------------------------------------
393
+ console.error(
394
+ \`[afterMerge] PR #\${context.prNumber} "\${context.prTitle}" merged (issue #\${context.issueNumber}, branch \${context.branch}).\`,
395
+ );
396
+
397
+ // -----------------------------------------------------------------------
398
+ // EXAMPLE IDEA — Slack notification. Uncomment and set SLACK_WEBHOOK_URL.
399
+ // -----------------------------------------------------------------------
400
+ // const webhookUrl = process.env.SLACK_WEBHOOK_URL;
401
+ // if (webhookUrl) {
402
+ // await fetch(webhookUrl, {
403
+ // method: 'POST',
404
+ // headers: { 'Content-Type': 'application/json' },
405
+ // body: JSON.stringify({ text: \`✅ Merged PR #\${context.prNumber}: \${context.prTitle}\` }),
406
+ // });
407
+ // console.error('[afterMerge] Slack notification sent.');
408
+ // }
409
+
410
+ // -----------------------------------------------------------------------
411
+ // EXAMPLE IDEA — tear down the per-launch database branch (Neon + Vercel)
412
+ // that a matching beforeLaunch.ts provisioned. Requires NEON_API_KEY,
413
+ // NEON_PROJECT_ID, VERCEL_TOKEN, VERCEL_PROJECT_ID. Treat "not found" as
414
+ // already-clean: log a warning and continue, don't throw — this hook may
415
+ // re-run, or the launch may have been killed before beforeLaunch ran.
416
+ // -----------------------------------------------------------------------
417
+ // const branchName = \`preview/\${context.branch}\`;
418
+ // // ...GET the Neon branch by name; if found, DELETE it; if not found,
419
+ // // console.error a warning and continue.
420
+ // // ...GET the Vercel branch-scoped DATABASE_URL env var by gitBranch; if
421
+ // // found, DELETE it; if not found, console.error a warning and continue.
422
+ }
423
+
424
+ main().catch((error) => {
425
+ console.error('[afterMerge] Hook failed (non-fatal):', error instanceof Error ? error.message : String(error));
426
+ });
427
+ `;
428
+ // Hook keys always written as a real (uncommented) config line by init. Each
429
+ // key gets its existing/configured value if the merged config already has one,
430
+ // otherwise the default path — which points at the working example file that
431
+ // generateHookTemplates() scaffolds. Order here is the order their lines are
432
+ // written to the generated config. Deliberately not every HooksSchema key —
433
+ // beforePreview, afterPreview, beforeMerge, and deploymentShutdown are valid
434
+ // config keys but are not suggested by init (see the fallback loop below,
435
+ // which still preserves any of them if a config already has one set).
436
+ const HOOK_KEY_DEFAULTS = [
437
+ { key: 'beforeLaunch', defaultPath: '.hublaunch/hooks/beforeLaunch.ts' },
438
+ { key: 'afterMerge', defaultPath: '.hublaunch/hooks/afterMerge.ts' },
439
+ ];
440
+ /**
441
+ * Build the `hooks: { ... }` config section. Each key in HOOK_KEY_DEFAULTS is
442
+ * always written as a real, uncommented line: the existing/merged config's
443
+ * value if it already has one (never overridden), otherwise the default path
444
+ * to the working example file that generateHookTemplates() scaffolds. Any
445
+ * other configured hook key (valid per HooksSchema, just not advertised) is
446
+ * still preserved as a real line — it is never dropped.
447
+ */
448
+ export function buildHooksConfigSection(hooks) {
449
+ const advertisedKeys = new Set(HOOK_KEY_DEFAULTS.map((d) => d.key));
450
+ const lines = HOOK_KEY_DEFAULTS.map(({ key, defaultPath }) => {
451
+ const value = hooks?.[key] ?? defaultPath;
452
+ return ` ${key}: ${JSON.stringify(value)},`;
453
+ });
454
+ for (const [key, value] of Object.entries(hooks ?? {})) {
455
+ if (value && !advertisedKeys.has(key)) {
456
+ lines.push(` ${key}: ${JSON.stringify(value)},`);
457
+ }
458
+ }
459
+ return `\n\n // Lifecycle hooks\n hooks: {\n${lines.join('\n')}\n },`;
460
+ }
426
461
  const ADAPTERS_README_TEMPLATE = `# HubLaunch Adapters
427
462
 
428
463
  This directory contains custom service adapters for extending HubLaunch functionality.
@@ -558,33 +593,29 @@ Templates are automatically detected and used by HubLaunch when creating issues
558
593
  \`\`\`
559
594
  `;
560
595
  /**
561
- * Generate hook templates based on authentication provider
596
+ * Generate hook directory structure with documentation
562
597
  */
563
- export async function generateHookTemplates(projectRoot, authProvider) {
598
+ export async function generateHookTemplates(projectRoot) {
564
599
  const hooksDir = join(projectRoot, '.hublaunch/hooks');
565
600
  // Create hooks directory
566
601
  await mkdir(hooksDir, { recursive: true });
567
602
  logger.success(`Created directory: .hublaunch/hooks/`);
568
- // Generate deploymentStartupScript.ts template based on auth provider
569
- // Only create if auth provider is configured (not "none"). An existing script
570
- // is never overwritten — the user may have customized it.
571
- if (authProvider !== 'none') {
572
- const startupScriptPath = join(hooksDir, 'deploymentStartupScript.ts');
573
- if (!existsSync(startupScriptPath)) {
574
- const template = getDeploymentStartupTemplate(authProvider);
575
- await writeFile(startupScriptPath, template, 'utf-8');
576
- logger.success(`Created: .hublaunch/hooks/deploymentStartupScript.ts`);
577
- }
578
- else {
579
- logger.info('Keeping existing deploymentStartupScript.ts');
580
- }
581
- }
582
603
  // Generate README for hooks
583
604
  const readmePath = join(hooksDir, 'README.md');
584
605
  if (!existsSync(readmePath)) {
585
606
  await writeFile(readmePath, HOOKS_README_TEMPLATE, 'utf-8');
586
607
  logger.success(`Created: .hublaunch/hooks/README.md`);
587
608
  }
609
+ const beforeLaunchPath = join(hooksDir, 'beforeLaunch.ts');
610
+ if (!existsSync(beforeLaunchPath)) {
611
+ await writeFile(beforeLaunchPath, BEFORE_LAUNCH_HOOK_TEMPLATE, 'utf-8');
612
+ logger.success(`Created: .hublaunch/hooks/beforeLaunch.ts`);
613
+ }
614
+ const afterMergePath = join(hooksDir, 'afterMerge.ts');
615
+ if (!existsSync(afterMergePath)) {
616
+ await writeFile(afterMergePath, AFTER_MERGE_HOOK_TEMPLATE, 'utf-8');
617
+ logger.success(`Created: .hublaunch/hooks/afterMerge.ts`);
618
+ }
588
619
  logger.blank();
589
620
  logger.info('📖 See .hublaunch/hooks/README.md for hook documentation');
590
621
  }
@@ -1076,12 +1107,6 @@ export async function executeInit(options) {
1076
1107
  },
1077
1108
  };
1078
1109
  }
1079
- // Add hooks if configured
1080
- if (authProvider !== 'none') {
1081
- baseConfig.hooks = {
1082
- deploymentStartup: '.hublaunch/hooks/deploymentStartupScript.ts',
1083
- };
1084
- }
1085
1110
  // Merge with existing config (existing values take precedence)
1086
1111
  const finalConfig = existingConfig
1087
1112
  ? mergeConfigs(existingConfig, baseConfig)
@@ -1104,10 +1129,11 @@ export async function executeInit(options) {
1104
1129
  const cliVersion = getCliVersion();
1105
1130
  if (cliVersion)
1106
1131
  finalConfig.version = cliVersion;
1107
- // Generate hooks config section (omitted entirely when not configured)
1108
- const hooksConfigSection = finalConfig.hooks
1109
- ? `\n\n // Lifecycle hooks\n hooks: ${JSON.stringify(finalConfig.hooks, null, 2).replace(/\n/g, '\n ')},`
1110
- : '';
1132
+ // Generate hooks config section. Always emitted: every known hook key gets
1133
+ // a line — a real, uncommented value when already configured (never
1134
+ // overridden), otherwise a commented-out placeholder so the available hook
1135
+ // points are discoverable without reading docs.
1136
+ const hooksConfigSection = buildHooksConfigSection(finalConfig.hooks);
1111
1137
  // Generate services config section without credentials (omitted when not configured)
1112
1138
  const servicesConfigSection = finalConfig.services
1113
1139
  ? `\n\n // Service configuration\n services: ${JSON.stringify(finalConfig.services, null, 2).replace(/\n/g, '\n ')},`
@@ -1289,10 +1315,10 @@ ${envVarsLine}${stepsLine}${buildPreservedSection(finalConfig)}
1289
1315
  logger.info(`â„šī¸ .env file already exists at ${envPath} - skipping to preserve your existing configuration`);
1290
1316
  logger.info(' Add any required environment variables manually if needed');
1291
1317
  }
1292
- // Always generate hooks directory structure (with README and optionally script)
1318
+ // Always generate hooks directory structure (with README)
1293
1319
  logger.blank();
1294
1320
  logger.info('Generating hooks directory structure...');
1295
- await generateHookTemplates(repoRoot, authProvider);
1321
+ await generateHookTemplates(repoRoot);
1296
1322
  // Always generate adapters directory structure
1297
1323
  logger.blank();
1298
1324
  logger.info('Generating adapter directory structure...');
@@ -1362,9 +1388,6 @@ ${envVarsLine}${stepsLine}${buildPreservedSection(finalConfig)}
1362
1388
  logger.listItem('- TEST_USER_PASSWORD: Test user password', 2);
1363
1389
  logger.blank();
1364
1390
  }
1365
- if (authProvider !== 'none') {
1366
- logger.listItem('Customize .hublaunch/hooks/deploymentStartupScript.ts as needed', 1);
1367
- }
1368
1391
  if (showLoginHint) {
1369
1392
  logger.listItem("Run 'hula login' to authenticate.", 1);
1370
1393
  }