@stacksolo/cli 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -635,6 +635,120 @@ async function checkAndFixCloudBuildPermissions(projectId, onLog) {
635
635
  };
636
636
  }
637
637
 
638
+ // src/gcp/firebase.ts
639
+ import { exec as exec5 } from "child_process";
640
+ import { promisify as promisify5 } from "util";
641
+ var execAsync5 = promisify5(exec5);
642
+ async function isFirebaseInstalled() {
643
+ try {
644
+ await execAsync5("which firebase");
645
+ return true;
646
+ } catch {
647
+ return false;
648
+ }
649
+ }
650
+ async function checkFirebaseAuth() {
651
+ try {
652
+ const { stdout } = await execAsync5("firebase login:list --json 2>/dev/null");
653
+ const result = JSON.parse(stdout);
654
+ if (result.status === "success" && result.result?.length > 0) {
655
+ return result.result[0].user?.email || result.result[0];
656
+ }
657
+ return null;
658
+ } catch {
659
+ try {
660
+ const { stdout } = await execAsync5("firebase projects:list --json 2>/dev/null");
661
+ const result = JSON.parse(stdout);
662
+ if (result.status === "success") {
663
+ return "authenticated";
664
+ }
665
+ } catch {
666
+ }
667
+ return null;
668
+ }
669
+ }
670
+ async function addFirebaseToProject(projectId) {
671
+ try {
672
+ await execAsync5(`firebase projects:addfirebase ${projectId} 2>&1`);
673
+ return { success: true };
674
+ } catch (error) {
675
+ let errorMessage = "";
676
+ if (error && typeof error === "object" && "stdout" in error) {
677
+ errorMessage = String(error.stdout);
678
+ }
679
+ if (!errorMessage && error && typeof error === "object" && "stderr" in error) {
680
+ errorMessage = String(error.stderr);
681
+ }
682
+ if (!errorMessage) {
683
+ errorMessage = String(error);
684
+ }
685
+ if (errorMessage.includes("already exists") || errorMessage.includes("ALREADY_EXISTS")) {
686
+ return { success: true };
687
+ }
688
+ if (errorMessage.includes("PERMISSION_DENIED")) {
689
+ return {
690
+ success: false,
691
+ error: "Permission denied. Make sure you have Firebase Admin permissions on the GCP project."
692
+ };
693
+ }
694
+ if (errorMessage.includes("NOT_FOUND")) {
695
+ return {
696
+ success: false,
697
+ error: "GCP project not found. Make sure the project ID is correct."
698
+ };
699
+ }
700
+ return { success: false, error: errorMessage || "Failed to add Firebase to project" };
701
+ }
702
+ }
703
+ function getFirebaseAuthConsoleUrl(projectId) {
704
+ return `https://console.firebase.google.com/project/${projectId}/authentication`;
705
+ }
706
+ function getBillingConsoleUrl(projectId) {
707
+ return `https://console.cloud.google.com/billing/linkedaccount?project=${projectId}`;
708
+ }
709
+ async function isBillingEnabled(projectId) {
710
+ try {
711
+ const { stdout } = await execAsync5(
712
+ `gcloud billing projects describe ${projectId} --format="value(billingEnabled)" 2>/dev/null`
713
+ );
714
+ return stdout.trim().toLowerCase() === "true";
715
+ } catch {
716
+ return false;
717
+ }
718
+ }
719
+ function generateProjectId(name) {
720
+ const cleanName = name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
721
+ const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
722
+ let suffix = "";
723
+ for (let i = 0; i < 6; i++) {
724
+ suffix += chars.charAt(Math.floor(Math.random() * chars.length));
725
+ }
726
+ const maxNameLen = 30 - 1 - suffix.length;
727
+ const truncatedName = cleanName.slice(0, maxNameLen);
728
+ return `${truncatedName}-${suffix}`;
729
+ }
730
+ function isValidProjectId(projectId) {
731
+ if (!projectId) {
732
+ return { valid: false, error: "Project ID is required" };
733
+ }
734
+ if (projectId.length < 6 || projectId.length > 30) {
735
+ return { valid: false, error: "Project ID must be 6-30 characters" };
736
+ }
737
+ if (!/^[a-z]/.test(projectId)) {
738
+ return { valid: false, error: "Project ID must start with a lowercase letter" };
739
+ }
740
+ if (!/[a-z0-9]$/.test(projectId)) {
741
+ return { valid: false, error: "Project ID must end with a letter or digit" };
742
+ }
743
+ if (!/^[a-z][a-z0-9-]*[a-z0-9]$/.test(projectId)) {
744
+ return {
745
+ valid: false,
746
+ error: "Project ID can only contain lowercase letters, digits, and hyphens"
747
+ };
748
+ }
749
+ return { valid: true };
750
+ }
751
+
638
752
  // src/templates/index.ts
639
753
  import * as fs from "fs/promises";
640
754
  import * as path from "path";
@@ -3390,6 +3504,386 @@ var UI_FRAMEWORKS = [
3390
3504
  description: "Simple HTML/CSS/JS - no build step"
3391
3505
  }
3392
3506
  ];
3507
+ async function waitForManualStep(description, url, instruction) {
3508
+ console.log(chalk.yellow(`
3509
+ ${description}
3510
+ `));
3511
+ console.log(chalk.white(` ${url}
3512
+ `));
3513
+ console.log(chalk.gray(` ${instruction}
3514
+ `));
3515
+ const { action } = await inquirer.prompt([
3516
+ {
3517
+ type: "list",
3518
+ name: "action",
3519
+ message: "What would you like to do?",
3520
+ choices: [
3521
+ { name: "Continue (I've completed this step)", value: "continue" },
3522
+ { name: "Open URL and wait", value: "open" },
3523
+ { name: "Quit and resume later", value: "quit" }
3524
+ ]
3525
+ }
3526
+ ]);
3527
+ if (action === "open") {
3528
+ const { exec: exec16 } = await import("child_process");
3529
+ const { promisify: promisify16 } = await import("util");
3530
+ const execAsync16 = promisify16(exec16);
3531
+ try {
3532
+ const platform = process.platform;
3533
+ if (platform === "darwin") {
3534
+ await execAsync16(`open "${url}"`);
3535
+ } else if (platform === "win32") {
3536
+ await execAsync16(`start "${url}"`);
3537
+ } else {
3538
+ await execAsync16(`xdg-open "${url}"`);
3539
+ }
3540
+ console.log(chalk.gray("\n Opened in browser. Complete the step and press Enter.\n"));
3541
+ } catch {
3542
+ console.log(chalk.gray("\n Could not open browser. Please open the URL manually.\n"));
3543
+ }
3544
+ await inquirer.prompt([
3545
+ {
3546
+ type: "input",
3547
+ name: "done",
3548
+ message: "Press Enter when done..."
3549
+ }
3550
+ ]);
3551
+ return { continue: true, retry: false };
3552
+ }
3553
+ if (action === "quit") {
3554
+ return { continue: false, retry: false };
3555
+ }
3556
+ return { continue: true, retry: false };
3557
+ }
3558
+ async function handleCreateProject(cwd, options) {
3559
+ console.log(chalk.cyan(BANNER));
3560
+ console.log(chalk.bold(" Create a new GCP + Firebase project\n"));
3561
+ console.log(chalk.gray("\u2500".repeat(75)));
3562
+ console.log(chalk.cyan.bold("\n Step 1: Prerequisites\n"));
3563
+ const gcloudSpinner = ora("Checking gcloud CLI...").start();
3564
+ if (!await isGcloudInstalled()) {
3565
+ gcloudSpinner.fail("gcloud CLI not found");
3566
+ console.log(chalk.red("\n gcloud CLI is required.\n"));
3567
+ console.log(chalk.gray(" Install: https://cloud.google.com/sdk/docs/install\n"));
3568
+ return;
3569
+ }
3570
+ const authInfo = await checkGcloudAuth();
3571
+ if (!authInfo) {
3572
+ gcloudSpinner.fail("Not authenticated to GCP");
3573
+ console.log(chalk.red("\n Please authenticate first:\n"));
3574
+ console.log(chalk.white(" gcloud auth login"));
3575
+ console.log(chalk.white(" gcloud auth application-default login\n"));
3576
+ return;
3577
+ }
3578
+ gcloudSpinner.succeed(`gcloud authenticated as ${chalk.green(authInfo.account)}`);
3579
+ const firebaseSpinner = ora("Checking Firebase CLI...").start();
3580
+ if (!await isFirebaseInstalled()) {
3581
+ firebaseSpinner.fail("Firebase CLI not found");
3582
+ console.log(chalk.red("\n Firebase CLI is required for --create-project.\n"));
3583
+ console.log(chalk.gray(" Install: npm install -g firebase-tools"));
3584
+ console.log(chalk.gray(" Then run: firebase login\n"));
3585
+ return;
3586
+ }
3587
+ const firebaseAuth = await checkFirebaseAuth();
3588
+ if (!firebaseAuth) {
3589
+ firebaseSpinner.fail("Not authenticated to Firebase");
3590
+ console.log(chalk.red("\n Please authenticate first:\n"));
3591
+ console.log(chalk.white(" firebase login\n"));
3592
+ return;
3593
+ }
3594
+ firebaseSpinner.succeed("Firebase CLI authenticated");
3595
+ console.log(chalk.gray("\n\u2500".repeat(75)));
3596
+ console.log(chalk.cyan.bold("\n Step 2: Project Details\n"));
3597
+ let projectName = options.name;
3598
+ if (!projectName) {
3599
+ const defaultName = path4.basename(cwd).toLowerCase().replace(/[^a-z0-9-]/g, "-");
3600
+ const { name } = await inquirer.prompt([
3601
+ {
3602
+ type: "input",
3603
+ name: "name",
3604
+ message: "Project name:",
3605
+ default: defaultName,
3606
+ validate: (input) => {
3607
+ if (!input) return "Project name is required";
3608
+ if (!/^[a-z][a-z0-9-]*$/.test(input)) {
3609
+ return "Must start with letter, only lowercase, numbers, hyphens";
3610
+ }
3611
+ return true;
3612
+ }
3613
+ }
3614
+ ]);
3615
+ projectName = name;
3616
+ }
3617
+ const suggestedId = generateProjectId(projectName);
3618
+ const { projectId } = await inquirer.prompt([
3619
+ {
3620
+ type: "input",
3621
+ name: "projectId",
3622
+ message: "GCP Project ID:",
3623
+ default: suggestedId,
3624
+ validate: (input) => {
3625
+ const result = isValidProjectId(input);
3626
+ return result.valid || result.error || "Invalid project ID";
3627
+ }
3628
+ }
3629
+ ]);
3630
+ let region = options.region;
3631
+ if (!region) {
3632
+ const regions = getRegionsForProvider("gcp");
3633
+ const { selectedRegion } = await inquirer.prompt([
3634
+ {
3635
+ type: "list",
3636
+ name: "selectedRegion",
3637
+ message: "Region:",
3638
+ choices: regions.map((r) => ({ name: r.name, value: r.value })),
3639
+ default: "us-central1"
3640
+ }
3641
+ ]);
3642
+ region = selectedRegion;
3643
+ }
3644
+ console.log(chalk.gray(`
3645
+ Project: ${chalk.white(projectName)}`));
3646
+ console.log(chalk.gray(` GCP ID: ${chalk.white(projectId)}`));
3647
+ console.log(chalk.gray(` Region: ${chalk.white(region)}`));
3648
+ console.log(chalk.gray("\n\u2500".repeat(75)));
3649
+ console.log(chalk.cyan.bold("\n Step 3: Create GCP Project\n"));
3650
+ const createSpinner = ora("Creating GCP project...").start();
3651
+ const createResult = await createProject(projectId, projectName);
3652
+ if (!createResult.success) {
3653
+ createSpinner.fail("Failed to create project");
3654
+ console.log(chalk.red(`
3655
+ ${createResult.error}
3656
+ `));
3657
+ return;
3658
+ }
3659
+ createSpinner.succeed(`Created GCP project: ${chalk.green(projectId)}`);
3660
+ await setActiveProject(projectId);
3661
+ console.log(chalk.gray("\n\u2500".repeat(75)));
3662
+ console.log(chalk.cyan.bold("\n Step 4: Enable Billing\n"));
3663
+ const billingSpinner = ora("Checking billing status...").start();
3664
+ let billingEnabled = await isBillingEnabled(projectId);
3665
+ if (billingEnabled) {
3666
+ billingSpinner.succeed("Billing is already enabled");
3667
+ } else {
3668
+ billingSpinner.stop();
3669
+ const billingAccounts = await listBillingAccounts();
3670
+ if (billingAccounts.length > 0) {
3671
+ const { billingAction } = await inquirer.prompt([
3672
+ {
3673
+ type: "list",
3674
+ name: "billingAction",
3675
+ message: "Link a billing account:",
3676
+ choices: [
3677
+ ...billingAccounts.map((b) => ({
3678
+ name: `${b.name} (${b.id})`,
3679
+ value: b.id
3680
+ })),
3681
+ new inquirer.Separator(),
3682
+ { name: "Configure manually in GCP Console", value: "__manual__" }
3683
+ ]
3684
+ }
3685
+ ]);
3686
+ if (billingAction !== "__manual__") {
3687
+ const linkSpinner = ora("Linking billing account...").start();
3688
+ const linked = await linkBillingAccount(projectId, billingAction);
3689
+ if (linked) {
3690
+ linkSpinner.succeed("Billing account linked");
3691
+ billingEnabled = true;
3692
+ } else {
3693
+ linkSpinner.warn("Could not link billing account automatically");
3694
+ }
3695
+ }
3696
+ }
3697
+ if (!billingEnabled) {
3698
+ const billingUrl = getBillingConsoleUrl(projectId);
3699
+ const result = await waitForManualStep(
3700
+ "Billing must be enabled to use GCP services.",
3701
+ billingUrl,
3702
+ "Link a billing account in the GCP Console, then continue."
3703
+ );
3704
+ if (!result.continue) {
3705
+ console.log(chalk.yellow("\n Project created but billing not enabled."));
3706
+ console.log(chalk.gray(` Resume setup later by running: stacksolo init --project-id ${projectId}
3707
+ `));
3708
+ return;
3709
+ }
3710
+ }
3711
+ }
3712
+ console.log(chalk.gray("\n\u2500".repeat(75)));
3713
+ console.log(chalk.cyan.bold("\n Step 5: Enable GCP APIs\n"));
3714
+ const apisSpinner = ora("Enabling required APIs...").start();
3715
+ const apiResult = await enableApis(projectId, REQUIRED_APIS, (api2, success) => {
3716
+ if (success) {
3717
+ apisSpinner.text = `Enabled ${api2}`;
3718
+ }
3719
+ });
3720
+ if (apiResult.failed.length === 0) {
3721
+ apisSpinner.succeed(`Enabled ${apiResult.enabled.length} APIs`);
3722
+ } else {
3723
+ apisSpinner.warn(`Enabled ${apiResult.enabled.length} APIs, ${apiResult.failed.length} failed`);
3724
+ console.log(chalk.yellow(" Failed APIs (may need billing):"));
3725
+ for (const api2 of apiResult.failed) {
3726
+ console.log(chalk.gray(` - ${api2}`));
3727
+ }
3728
+ }
3729
+ console.log(chalk.gray("\n\u2500".repeat(75)));
3730
+ console.log(chalk.cyan.bold("\n Step 6: Add Firebase\n"));
3731
+ const firebaseAddSpinner = ora("Adding Firebase to project...").start();
3732
+ const firebaseResult = await addFirebaseToProject(projectId);
3733
+ if (!firebaseResult.success) {
3734
+ firebaseAddSpinner.fail("Failed to add Firebase");
3735
+ console.log(chalk.red(`
3736
+ ${firebaseResult.error}
3737
+ `));
3738
+ console.log(chalk.gray(" You can add Firebase manually later:"));
3739
+ console.log(chalk.white(` firebase projects:addfirebase ${projectId}
3740
+ `));
3741
+ } else {
3742
+ firebaseAddSpinner.succeed("Firebase added to project");
3743
+ }
3744
+ console.log(chalk.gray("\n\u2500".repeat(75)));
3745
+ console.log(chalk.cyan.bold("\n Step 7: Configure Firebase Authentication\n"));
3746
+ const authUrl = getFirebaseAuthConsoleUrl(projectId);
3747
+ const { needsAuth } = await inquirer.prompt([
3748
+ {
3749
+ type: "confirm",
3750
+ name: "needsAuth",
3751
+ message: "Do you need Firebase Authentication?",
3752
+ default: true
3753
+ }
3754
+ ]);
3755
+ if (needsAuth) {
3756
+ const authResult = await waitForManualStep(
3757
+ "Enable authentication providers in Firebase Console.",
3758
+ authUrl,
3759
+ 'Click "Get Started", then enable Email/Password, Google, or other providers.'
3760
+ );
3761
+ if (!authResult.continue) {
3762
+ console.log(chalk.yellow("\n You can configure auth later at:"));
3763
+ console.log(chalk.gray(` ${authUrl}
3764
+ `));
3765
+ }
3766
+ }
3767
+ console.log(chalk.gray("\n\u2500".repeat(75)));
3768
+ console.log(chalk.cyan.bold("\n Step 8: Configure Permissions\n"));
3769
+ const policySpinner = ora("Checking org policy...").start();
3770
+ const policyStatus = await checkOrgPolicy(projectId);
3771
+ if (policyStatus.hasRestriction && policyStatus.canOverride) {
3772
+ policySpinner.text = "Fixing org policy...";
3773
+ const fixed = await fixOrgPolicy(projectId);
3774
+ if (fixed) {
3775
+ policySpinner.succeed("Org policy configured for public access");
3776
+ } else {
3777
+ policySpinner.warn("Could not update org policy - some features may be limited");
3778
+ }
3779
+ } else if (policyStatus.hasRestriction) {
3780
+ policySpinner.warn("Org policy restricts public access - contact your admin");
3781
+ } else {
3782
+ policySpinner.succeed("No org policy restrictions");
3783
+ }
3784
+ const iamSpinner = ora("Configuring Cloud Build permissions...").start();
3785
+ const iamResult = await checkAndFixCloudBuildPermissions(projectId);
3786
+ if (iamResult.failed.length === 0) {
3787
+ iamSpinner.succeed("Cloud Build permissions configured");
3788
+ } else {
3789
+ iamSpinner.warn("Some permissions could not be set");
3790
+ }
3791
+ console.log(chalk.gray("\n\u2500".repeat(75)));
3792
+ console.log(chalk.cyan.bold("\n Step 9: Generate Project Files\n"));
3793
+ const { projectType } = await inquirer.prompt([
3794
+ {
3795
+ type: "list",
3796
+ name: "projectType",
3797
+ message: "What are you building?",
3798
+ choices: PROJECT_TYPES.map((t) => ({
3799
+ name: `${t.name}
3800
+ ${chalk.gray(t.description)}`,
3801
+ value: t.value,
3802
+ short: t.name
3803
+ })),
3804
+ default: "ui-api"
3805
+ }
3806
+ ]);
3807
+ let uiFramework;
3808
+ if (projectType === "ui-api" || projectType === "ui-only") {
3809
+ const { selectedFramework } = await inquirer.prompt([
3810
+ {
3811
+ type: "list",
3812
+ name: "selectedFramework",
3813
+ message: "Which UI framework?",
3814
+ choices: UI_FRAMEWORKS.map((f) => ({
3815
+ name: `${f.name}
3816
+ ${chalk.gray(f.description)}`,
3817
+ value: f.value,
3818
+ short: f.name
3819
+ })),
3820
+ default: "react"
3821
+ }
3822
+ ]);
3823
+ uiFramework = selectedFramework;
3824
+ }
3825
+ const generateSpinner = ora("Generating configuration...").start();
3826
+ const config = generateConfig({
3827
+ projectName,
3828
+ gcpProjectId: projectId,
3829
+ region,
3830
+ projectType,
3831
+ uiFramework,
3832
+ needsDatabase: false,
3833
+ needsBucket: false
3834
+ });
3835
+ if (needsAuth) {
3836
+ config.project.gcpKernel = {
3837
+ name: "kernel",
3838
+ firebaseProjectId: projectId,
3839
+ storageBucket: `${projectId}.firebasestorage.app`
3840
+ };
3841
+ if (!config.project.plugins) {
3842
+ config.project.plugins = [];
3843
+ }
3844
+ if (!config.project.plugins.includes("@stacksolo/plugin-gcp-kernel")) {
3845
+ config.project.plugins.push("@stacksolo/plugin-gcp-kernel");
3846
+ }
3847
+ }
3848
+ await createStacksoloDir(cwd, {
3849
+ gcpProjectId: projectId,
3850
+ orgPolicyFixed: !policyStatus.hasRestriction || policyStatus.canOverride,
3851
+ apisEnabled: REQUIRED_APIS
3852
+ });
3853
+ await createConfigFile(cwd, config);
3854
+ const scaffoldedFiles = await scaffoldTemplates(cwd, projectType, uiFramework);
3855
+ generateSpinner.succeed("Project files created");
3856
+ try {
3857
+ const registry3 = getRegistry();
3858
+ const configPath = path4.join(cwd, ".stacksolo", "stacksolo.config.json");
3859
+ await registry3.registerProject({
3860
+ name: projectName,
3861
+ gcpProjectId: projectId,
3862
+ region,
3863
+ configPath
3864
+ });
3865
+ } catch {
3866
+ }
3867
+ console.log(chalk.gray("\n\u2500".repeat(75)));
3868
+ console.log(chalk.bold.green("\n Success! Your project is ready.\n"));
3869
+ console.log(chalk.white(" Created:"));
3870
+ console.log(chalk.green(` \u2713 GCP Project: ${projectId}`));
3871
+ console.log(chalk.green(` \u2713 Firebase Project: ${projectId}`));
3872
+ if (needsAuth) {
3873
+ console.log(chalk.green(" \u2713 Firebase Auth enabled"));
3874
+ }
3875
+ console.log(chalk.green(" \u2713 .stacksolo/stacksolo.config.json"));
3876
+ console.log(chalk.gray("\n Next steps:\n"));
3877
+ console.log(chalk.white(" 1. Review your config:"));
3878
+ console.log(chalk.cyan(" cat .stacksolo/stacksolo.config.json"));
3879
+ console.log(chalk.white(" 2. Install dependencies:"));
3880
+ console.log(chalk.cyan(" npm install"));
3881
+ console.log(chalk.white(" 3. Start development:"));
3882
+ console.log(chalk.cyan(" stacksolo dev"));
3883
+ console.log(chalk.white(" 4. Deploy to GCP:"));
3884
+ console.log(chalk.cyan(" stacksolo deploy"));
3885
+ console.log("");
3886
+ }
3393
3887
  async function handleRemoteTemplate(cwd, options) {
3394
3888
  console.log(chalk.cyan(BANNER));
3395
3889
  console.log(chalk.bold(` Initializing from template: ${options.template}
@@ -3600,8 +4094,11 @@ async function handleRemoteTemplate(cwd, options) {
3600
4094
  `));
3601
4095
  }
3602
4096
  }
3603
- var initCommand = new Command("init").description("Initialize a new StackSolo project").option("-n, --name <name>", "Project name").option("--project-id <id>", "GCP project ID").option("-r, --region <region>", "Region").option("-t, --template <template>", "Project template (function-api, container-api, firebase-app, etc.)").option("-y, --yes", "Skip prompts and use defaults").option("--skip-org-policy", "Skip org policy check and fix").option("--skip-apis", "Skip enabling GCP APIs").option("--list-templates", "List available remote templates").action(async (options) => {
4097
+ var initCommand = new Command("init").description("Initialize a new StackSolo project").option("-n, --name <name>", "Project name").option("--project-id <id>", "GCP project ID").option("-r, --region <region>", "Region").option("-t, --template <template>", "Project template (function-api, container-api, firebase-app, etc.)").option("-y, --yes", "Skip prompts and use defaults").option("--skip-org-policy", "Skip org policy check and fix").option("--skip-apis", "Skip enabling GCP APIs").option("--list-templates", "List available remote templates").option("--create-project", "Create a new GCP + Firebase project").action(async (options) => {
3604
4098
  const cwd = process.cwd();
4099
+ if (options.createProject) {
4100
+ return await handleCreateProject(cwd, options);
4101
+ }
3605
4102
  if (options.listTemplates) {
3606
4103
  const spinner = ora("Fetching available templates...").start();
3607
4104
  try {
@@ -4717,7 +5214,7 @@ function resolveContainer(container, networkName, defaultRegion, networkId, regi
4717
5214
  }
4718
5215
  return {
4719
5216
  id: `container-${container.name}`,
4720
- type: "gcp:cloud_run",
5217
+ type: "gcp-cdktf:cloud_run",
4721
5218
  name: container.name,
4722
5219
  config: {
4723
5220
  name: container.name,
@@ -4775,7 +5272,7 @@ function resolveFunction(fn, networkName, defaultRegion, networkId, registryId)
4775
5272
  }
4776
5273
  return {
4777
5274
  id: `function-${fn.name}`,
4778
- type: "gcp:cloud_function",
5275
+ type: "gcp-cdktf:cloud_function",
4779
5276
  name: fn.name,
4780
5277
  config: {
4781
5278
  name: fn.name,
@@ -5091,6 +5588,10 @@ function resolveCdktfConfig(config, projectInfo) {
5091
5588
  const functionId = `function-${fn.name}`;
5092
5589
  functionIds.push(functionId);
5093
5590
  functionNames.push(functionName);
5591
+ const functionEnv = {
5592
+ ...hasGcpKernel ? { GOOGLE_CLOUD_PROJECT: projectInfo.gcpProjectId } : {},
5593
+ ...fn.env
5594
+ };
5094
5595
  resources.push({
5095
5596
  id: functionId,
5096
5597
  type: "gcp-cdktf:cloud_function",
@@ -5108,7 +5609,8 @@ function resolveCdktfConfig(config, projectInfo) {
5108
5609
  vpcConnector: connectorName,
5109
5610
  allowUnauthenticated: fn.allowUnauthenticated ?? true,
5110
5611
  projectId: projectInfo.gcpProjectId,
5111
- projectName: projectInfo.name
5612
+ projectName: projectInfo.name,
5613
+ environmentVariables: Object.keys(functionEnv).length > 0 ? functionEnv : void 0
5112
5614
  },
5113
5615
  dependsOn: [connectorId],
5114
5616
  network: network.name
@@ -9197,17 +9699,17 @@ import ora4 from "ora";
9197
9699
  import * as path14 from "path";
9198
9700
  import * as fs11 from "fs/promises";
9199
9701
  import { homedir as homedir3 } from "os";
9200
- import { spawn as spawn4, exec as exec10 } from "child_process";
9201
- import { promisify as promisify10 } from "util";
9702
+ import { spawn as spawn4, exec as exec11 } from "child_process";
9703
+ import { promisify as promisify11 } from "util";
9202
9704
 
9203
9705
  // src/services/deploy.service.ts
9204
9706
  import * as fs7 from "fs/promises";
9205
9707
  import * as path9 from "path";
9206
- import { exec as exec5, spawn as spawn2 } from "child_process";
9207
- import { promisify as promisify5 } from "util";
9708
+ import { exec as exec6, spawn as spawn2 } from "child_process";
9709
+ import { promisify as promisify6 } from "util";
9208
9710
  init_plugin_loader_service();
9209
9711
  import { registry as registry2 } from "@stacksolo/core";
9210
- var execAsync5 = promisify5(exec5);
9712
+ var execAsync6 = promisify6(exec6);
9211
9713
  async function execStreaming(command, options = {}) {
9212
9714
  return new Promise((resolve7, reject) => {
9213
9715
  const { cwd, onOutput, timeout = 3e5 } = options;
@@ -9267,7 +9769,11 @@ async function deployConfig(config, _stateDir, options = {}) {
9267
9769
  };
9268
9770
  try {
9269
9771
  if (!pluginsLoaded) {
9270
- await loadPlugins(config.project.plugins);
9772
+ const pluginsToLoad = [...config.project.plugins || []];
9773
+ if (config.project.gcpKernel && !pluginsToLoad.includes("@stacksolo/plugin-gcp-kernel")) {
9774
+ pluginsToLoad.push("@stacksolo/plugin-gcp-kernel");
9775
+ }
9776
+ await loadPlugins(pluginsToLoad);
9271
9777
  pluginsLoaded = true;
9272
9778
  }
9273
9779
  const resolved = resolveConfig(config);
@@ -9329,11 +9835,11 @@ async function deployConfig(config, _stateDir, options = {}) {
9329
9835
  await fs7.access(nodeModulesPath);
9330
9836
  } catch {
9331
9837
  log(`Installing dependencies for ${fnName}...`);
9332
- await execAsync5("npm install", { cwd: sourceDir, timeout: 12e4 });
9838
+ await execAsync6("npm install", { cwd: sourceDir, timeout: 12e4 });
9333
9839
  }
9334
9840
  if (packageJson2.scripts?.build) {
9335
9841
  log(`Building ${fnName} (TypeScript)...`);
9336
- await execAsync5("npm run build", { cwd: sourceDir, timeout: 6e4 });
9842
+ await execAsync6("npm run build", { cwd: sourceDir, timeout: 6e4 });
9337
9843
  isTypeScriptProject = true;
9338
9844
  }
9339
9845
  } catch {
@@ -9342,7 +9848,7 @@ async function deployConfig(config, _stateDir, options = {}) {
9342
9848
  const distDir = path9.join(sourceDir, "dist");
9343
9849
  const stagingDir = path9.join(workDir, `staging-${fnName}`);
9344
9850
  await fs7.mkdir(stagingDir, { recursive: true });
9345
- await execAsync5(`cp -r "${distDir}"/* "${stagingDir}"/`, { timeout: 3e4 });
9851
+ await execAsync6(`cp -r "${distDir}"/* "${stagingDir}"/`, { timeout: 3e4 });
9346
9852
  const pkgContent = await fs7.readFile(packageJsonPath, "utf-8");
9347
9853
  const pkg2 = JSON.parse(pkgContent);
9348
9854
  if (pkg2.main && pkg2.main.startsWith("dist/")) {
@@ -9355,10 +9861,10 @@ async function deployConfig(config, _stateDir, options = {}) {
9355
9861
  }
9356
9862
  delete pkg2.devDependencies;
9357
9863
  await fs7.writeFile(path9.join(stagingDir, "package.json"), JSON.stringify(pkg2, null, 2));
9358
- await execAsync5(`cd "${stagingDir}" && zip -r "${sourceZipPath}" .`, { timeout: 6e4 });
9864
+ await execAsync6(`cd "${stagingDir}" && zip -r "${sourceZipPath}" .`, { timeout: 6e4 });
9359
9865
  await fs7.rm(stagingDir, { recursive: true, force: true });
9360
9866
  } else {
9361
- await execAsync5(`cd "${sourceDir}" && zip -r "${sourceZipPath}" . -x "*.git*" -x "node_modules/*"`, { timeout: 6e4 });
9867
+ await execAsync6(`cd "${sourceDir}" && zip -r "${sourceZipPath}" . -x "*.git*" -x "node_modules/*"`, { timeout: 6e4 });
9362
9868
  }
9363
9869
  sourceZips.push({ name: fnName, zipPath: sourceZipPath });
9364
9870
  }
@@ -9366,7 +9872,7 @@ async function deployConfig(config, _stateDir, options = {}) {
9366
9872
  if (!preview && gcpKernelResources.length > 0) {
9367
9873
  log(`Configuring Docker authentication for gcr.io...`);
9368
9874
  try {
9369
- await execAsync5(`gcloud auth configure-docker gcr.io --quiet`, { timeout: 3e4 });
9875
+ await execAsync6(`gcloud auth configure-docker gcr.io --quiet`, { timeout: 3e4 });
9370
9876
  } catch (error) {
9371
9877
  log(`Warning: Failed to configure Docker auth for GCR: ${error instanceof Error ? error.message : error}`);
9372
9878
  }
@@ -9407,11 +9913,11 @@ async function deployConfig(config, _stateDir, options = {}) {
9407
9913
  await fs7.access(nodeModulesPath);
9408
9914
  } catch {
9409
9915
  log(`Installing dependencies for GCP Kernel...`);
9410
- await execAsync5("npm install", { cwd: kernelSourceDir, timeout: 12e4 });
9916
+ await execAsync6("npm install", { cwd: kernelSourceDir, timeout: 12e4 });
9411
9917
  }
9412
9918
  if (pkg2.scripts?.build) {
9413
9919
  log(`Building GCP Kernel TypeScript...`);
9414
- await execAsync5("npm run build", { cwd: kernelSourceDir, timeout: 6e4 });
9920
+ await execAsync6("npm run build", { cwd: kernelSourceDir, timeout: 6e4 });
9415
9921
  }
9416
9922
  } catch (buildError) {
9417
9923
  log(`Warning: Could not build kernel TypeScript: ${buildError instanceof Error ? buildError.message : buildError}`);
@@ -9426,7 +9932,7 @@ async function deployConfig(config, _stateDir, options = {}) {
9426
9932
  throw new Error(buildResult.stderr || "Docker build failed");
9427
9933
  }
9428
9934
  } else {
9429
- await execAsync5(`docker build --platform linux/amd64 -t "${kernelImage}" .`, { cwd: kernelSourceDir, timeout: 3e5 });
9935
+ await execAsync6(`docker build --platform linux/amd64 -t "${kernelImage}" .`, { cwd: kernelSourceDir, timeout: 3e5 });
9430
9936
  }
9431
9937
  log(`Pushing Docker image: ${kernelImage}`);
9432
9938
  if (verbose) {
@@ -9438,7 +9944,7 @@ async function deployConfig(config, _stateDir, options = {}) {
9438
9944
  throw new Error(pushResult.stderr || "Docker push failed");
9439
9945
  }
9440
9946
  } else {
9441
- await execAsync5(`docker push "${kernelImage}"`, { timeout: 3e5 });
9947
+ await execAsync6(`docker push "${kernelImage}"`, { timeout: 3e5 });
9442
9948
  }
9443
9949
  log(`GCP Kernel built and pushed successfully`);
9444
9950
  }
@@ -9486,9 +9992,9 @@ async function deployConfig(config, _stateDir, options = {}) {
9486
9992
  };
9487
9993
  await fs7.writeFile(path9.join(workDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
9488
9994
  log("Installing npm dependencies...");
9489
- await execAsync5("npm install", { cwd: workDir, timeout: 12e4 });
9995
+ await execAsync6("npm install", { cwd: workDir, timeout: 12e4 });
9490
9996
  log("Synthesizing Terraform configuration...");
9491
- await execAsync5("npx ts-node main.ts", { cwd: workDir, timeout: 6e4 });
9997
+ await execAsync6("npx ts-node main.ts", { cwd: workDir, timeout: 6e4 });
9492
9998
  const stackDir = path9.join(workDir, "cdktf.out", "stacks", "main");
9493
9999
  for (const { name, zipPath } of sourceZips) {
9494
10000
  await fs7.copyFile(zipPath, path9.join(stackDir, `${name}-source.zip`));
@@ -9505,7 +10011,7 @@ terraform {
9505
10011
  if (verbose) {
9506
10012
  await execStreaming("terraform init", { cwd: stackDir, onOutput: verboseLog });
9507
10013
  } else {
9508
- await execAsync5("terraform init", { cwd: stackDir });
10014
+ await execAsync6("terraform init", { cwd: stackDir });
9509
10015
  }
9510
10016
  if (destroy) {
9511
10017
  log("Destroying resources...");
@@ -9519,7 +10025,7 @@ terraform {
9519
10025
  throw new Error(result.stderr || "Terraform destroy failed");
9520
10026
  }
9521
10027
  } else {
9522
- await execAsync5("terraform destroy -auto-approve", { cwd: stackDir });
10028
+ await execAsync6("terraform destroy -auto-approve", { cwd: stackDir });
9523
10029
  }
9524
10030
  return {
9525
10031
  success: true,
@@ -9532,7 +10038,7 @@ terraform {
9532
10038
  if (verbose) {
9533
10039
  await execStreaming("terraform plan", { cwd: stackDir, onOutput: verboseLog });
9534
10040
  } else {
9535
- const { stdout } = await execAsync5("terraform plan", { cwd: stackDir });
10041
+ const { stdout } = await execAsync6("terraform plan", { cwd: stackDir });
9536
10042
  log(stdout);
9537
10043
  }
9538
10044
  return {
@@ -9554,7 +10060,7 @@ terraform {
9554
10060
  throw new Error(result.stderr || "Terraform apply failed");
9555
10061
  }
9556
10062
  } else {
9557
- await execAsync5("terraform apply -auto-approve", { cwd: stackDir });
10063
+ await execAsync6("terraform apply -auto-approve", { cwd: stackDir });
9558
10064
  }
9559
10065
  } catch (applyError) {
9560
10066
  const errorStr = String(applyError);
@@ -9570,7 +10076,7 @@ terraform {
9570
10076
  const region = config.project.region;
9571
10077
  log(`Configuring Docker authentication for ${region}-docker.pkg.dev...`);
9572
10078
  try {
9573
- await execAsync5(`gcloud auth configure-docker ${region}-docker.pkg.dev --quiet`, { timeout: 3e4 });
10079
+ await execAsync6(`gcloud auth configure-docker ${region}-docker.pkg.dev --quiet`, { timeout: 3e4 });
9574
10080
  } catch (error) {
9575
10081
  log(`Warning: Failed to configure Docker auth: ${error instanceof Error ? error.message : error}`);
9576
10082
  log("You may need to run: gcloud auth configure-docker " + region + "-docker.pkg.dev");
@@ -9601,11 +10107,11 @@ terraform {
9601
10107
  await fs7.access(nodeModulesPath);
9602
10108
  } catch {
9603
10109
  log(`Installing dependencies for ${containerName}...`);
9604
- await execAsync5("npm install", { cwd: sourceDir, timeout: 12e4 });
10110
+ await execAsync6("npm install", { cwd: sourceDir, timeout: 12e4 });
9605
10111
  }
9606
10112
  if (pkg2.scripts?.build) {
9607
10113
  log(`Building ${containerName}...`);
9608
- await execAsync5("npm run build", { cwd: sourceDir, timeout: 6e4 });
10114
+ await execAsync6("npm run build", { cwd: sourceDir, timeout: 6e4 });
9609
10115
  }
9610
10116
  } catch {
9611
10117
  }
@@ -9620,7 +10126,7 @@ terraform {
9620
10126
  throw new Error(buildResult.stderr || "Docker build failed");
9621
10127
  }
9622
10128
  } else {
9623
- await execAsync5(`docker build -t "${image}" .`, { cwd: sourceDir, timeout: 3e5 });
10129
+ await execAsync6(`docker build -t "${image}" .`, { cwd: sourceDir, timeout: 3e5 });
9624
10130
  }
9625
10131
  log(`Pushing Docker image: ${image}`);
9626
10132
  if (verbose) {
@@ -9632,7 +10138,7 @@ terraform {
9632
10138
  throw new Error(pushResult.stderr || "Docker push failed");
9633
10139
  }
9634
10140
  } else {
9635
- await execAsync5(`docker push "${image}"`, { timeout: 3e5 });
10141
+ await execAsync6(`docker push "${image}"`, { timeout: 3e5 });
9636
10142
  }
9637
10143
  log(`Container ${containerName} built and pushed successfully`);
9638
10144
  }
@@ -9648,11 +10154,11 @@ terraform {
9648
10154
  throw new Error(result.stderr || "Terraform re-apply failed");
9649
10155
  }
9650
10156
  } else {
9651
- await execAsync5("terraform apply -auto-approve", { cwd: stackDir });
10157
+ await execAsync6("terraform apply -auto-approve", { cwd: stackDir });
9652
10158
  }
9653
10159
  }
9654
10160
  }
9655
- const { stdout: outputJson } = await execAsync5("terraform output -json", { cwd: stackDir });
10161
+ const { stdout: outputJson } = await execAsync6("terraform output -json", { cwd: stackDir });
9656
10162
  const outputs = JSON.parse(outputJson || "{}");
9657
10163
  const outputValues = {};
9658
10164
  for (const [key, value] of Object.entries(outputs)) {
@@ -9703,10 +10209,10 @@ terraform {
9703
10209
  await fs7.access(nodeModulesPath);
9704
10210
  } catch {
9705
10211
  log(`Installing dependencies for ${uiName}...`);
9706
- await execAsync5("npm install", { cwd: sourceDir, timeout: 12e4 });
10212
+ await execAsync6("npm install", { cwd: sourceDir, timeout: 12e4 });
9707
10213
  }
9708
10214
  log(`Building ${uiName} (${detectedFramework})...`);
9709
- await execAsync5(buildCommand2, { cwd: sourceDir, timeout: 12e4 });
10215
+ await execAsync6(buildCommand2, { cwd: sourceDir, timeout: 12e4 });
9710
10216
  } else {
9711
10217
  distPath = sourceDir;
9712
10218
  }
@@ -9718,7 +10224,7 @@ terraform {
9718
10224
  continue;
9719
10225
  }
9720
10226
  log(`Uploading ${uiName} to gs://${bucketName}...`);
9721
- await execAsync5(`gsutil -m rsync -r -d "${distPath}" gs://${bucketName}`, { timeout: 3e5 });
10227
+ await execAsync6(`gsutil -m rsync -r -d "${distPath}" gs://${bucketName}`, { timeout: 3e5 });
9722
10228
  log(`UI ${uiName} deployed to gs://${bucketName}`);
9723
10229
  }
9724
10230
  }
@@ -9729,7 +10235,7 @@ terraform {
9729
10235
  log("Provisioning IAP service agent...");
9730
10236
  let iapServiceAccount = null;
9731
10237
  try {
9732
- const { stdout: identityOutput } = await execAsync5(
10238
+ const { stdout: identityOutput } = await execAsync6(
9733
10239
  `gcloud beta services identity create --service=iap.googleapis.com --project=${gcpProjectId} 2>&1`,
9734
10240
  { timeout: 6e4 }
9735
10241
  );
@@ -9761,7 +10267,7 @@ terraform {
9761
10267
  const region = config.project.region;
9762
10268
  log(`Granting IAP service account invoker role on Cloud Run: ${cloudRunServiceName}`);
9763
10269
  try {
9764
- await execAsync5(
10270
+ await execAsync6(
9765
10271
  `gcloud run services add-iam-policy-binding ${cloudRunServiceName} --region=${region} --project=${gcpProjectId} --member="serviceAccount:${iapServiceAccount}" --role="roles/run.invoker"`,
9766
10272
  { timeout: 6e4 }
9767
10273
  );
@@ -9774,7 +10280,7 @@ terraform {
9774
10280
  }
9775
10281
  log(`Enabling IAP on backend: ${backendServiceName}`);
9776
10282
  try {
9777
- await execAsync5(
10283
+ await execAsync6(
9778
10284
  `gcloud compute backend-services update ${backendServiceName} --global --project=${gcpProjectId} --iap=enabled`,
9779
10285
  { timeout: 6e4 }
9780
10286
  );
@@ -9782,7 +10288,7 @@ terraform {
9782
10288
  for (const member of allowedMembers) {
9783
10289
  log(`Granting IAP access to: ${member}`);
9784
10290
  try {
9785
- await execAsync5(
10291
+ await execAsync6(
9786
10292
  `gcloud iap web add-iam-policy-binding --resource-type=backend-services --service=${backendServiceName} --project=${gcpProjectId} --member="${member}" --role="roles/iap.httpsResourceAccessor"`,
9787
10293
  { timeout: 6e4 }
9788
10294
  );
@@ -9863,8 +10369,8 @@ function generateCdktfMain(config, generated) {
9863
10369
  // src/services/k8s-deploy.service.ts
9864
10370
  import * as fs8 from "fs/promises";
9865
10371
  import * as path10 from "path";
9866
- import { exec as exec6 } from "child_process";
9867
- import { promisify as promisify6 } from "util";
10372
+ import { exec as exec7 } from "child_process";
10373
+ import { promisify as promisify7 } from "util";
9868
10374
 
9869
10375
  // src/generators/k8s/yaml.ts
9870
10376
  function toYaml(obj, indent = 0) {
@@ -9941,7 +10447,7 @@ function combineYamlDocuments(documents) {
9941
10447
  }
9942
10448
 
9943
10449
  // src/services/k8s-deploy.service.ts
9944
- var execAsync6 = promisify6(exec6);
10450
+ var execAsync7 = promisify7(exec7);
9945
10451
  async function deployToKubernetes(options) {
9946
10452
  const { config, resources, imageTag = "latest", dryRun = false, onLog = console.log, onVerbose } = options;
9947
10453
  const project = config.project;
@@ -9976,7 +10482,7 @@ async function deployToKubernetes(options) {
9976
10482
  log("Applying manifests to cluster...");
9977
10483
  const kubectlArgs = k8sConfig.context ? `--context ${k8sConfig.context}` : "";
9978
10484
  const kubeconfigArg = k8sConfig.kubeconfig ? `--kubeconfig ${k8sConfig.kubeconfig}` : "";
9979
- await execAsync6(
10485
+ await execAsync7(
9980
10486
  `kubectl apply -f "${manifestDir}" ${kubectlArgs} ${kubeconfigArg}`.trim(),
9981
10487
  { timeout: 12e4 }
9982
10488
  );
@@ -9986,7 +10492,7 @@ async function deployToKubernetes(options) {
9986
10492
  const deploymentName = deployment.config.name;
9987
10493
  verbose?.(` Waiting for ${deploymentName}...`);
9988
10494
  try {
9989
- await execAsync6(
10495
+ await execAsync7(
9990
10496
  `kubectl rollout status deployment/${deploymentName} -n ${namespace} --timeout=120s ${kubectlArgs} ${kubeconfigArg}`.trim(),
9991
10497
  { timeout: 13e4 }
9992
10498
  );
@@ -10054,11 +10560,11 @@ async function buildAndPushImages(config, resources, tag = "latest", options = {
10054
10560
  await generateFunctionDockerfile(fullSourceDir, runtime);
10055
10561
  }
10056
10562
  verboseLog?.(` docker build -t ${imageUrl} ${fullSourceDir}`);
10057
- await execAsync6(`docker build -t "${imageUrl}" "${fullSourceDir}"`, {
10563
+ await execAsync7(`docker build -t "${imageUrl}" "${fullSourceDir}"`, {
10058
10564
  timeout: 3e5
10059
10565
  });
10060
10566
  log(`Pushing ${imageUrl}...`);
10061
- await execAsync6(`docker push "${imageUrl}"`, { timeout: 3e5 });
10567
+ await execAsync7(`docker push "${imageUrl}"`, { timeout: 3e5 });
10062
10568
  builtImages.push(imageUrl);
10063
10569
  }
10064
10570
  return {
@@ -10345,7 +10851,7 @@ async function checkKubernetesConnection(context, kubeconfig) {
10345
10851
  try {
10346
10852
  const contextArg = context ? `--context ${context}` : "";
10347
10853
  const kubeconfigArg = kubeconfig ? `--kubeconfig ${kubeconfig}` : "";
10348
- await execAsync6(
10854
+ await execAsync7(
10349
10855
  `kubectl cluster-info ${contextArg} ${kubeconfigArg}`.trim(),
10350
10856
  { timeout: 1e4 }
10351
10857
  );
@@ -10487,8 +10993,8 @@ import path13 from "path";
10487
10993
  import * as fs10 from "fs/promises";
10488
10994
  import chalk4 from "chalk";
10489
10995
  import inquirer3 from "inquirer";
10490
- import { exec as exec9 } from "child_process";
10491
- import { promisify as promisify9 } from "util";
10996
+ import { exec as exec10 } from "child_process";
10997
+ import { promisify as promisify10 } from "util";
10492
10998
 
10493
10999
  // src/services/terraform-state.service.ts
10494
11000
  import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
@@ -10583,15 +11089,15 @@ function isResourceInState(gcpResource, state) {
10583
11089
  }
10584
11090
 
10585
11091
  // src/services/gcp-scanner.service.ts
10586
- import { exec as exec7 } from "child_process";
10587
- import { promisify as promisify7 } from "util";
10588
- var execAsync7 = promisify7(exec7);
11092
+ import { exec as exec8 } from "child_process";
11093
+ import { promisify as promisify8 } from "util";
11094
+ var execAsync8 = promisify8(exec8);
10589
11095
  function matchesProjectPattern(resourceName, projectName, gcpProjectId) {
10590
11096
  return resourceName.startsWith(`${projectName}-`) || resourceName.startsWith(`${gcpProjectId}-${projectName}-`);
10591
11097
  }
10592
11098
  async function runGcloudCommand(command, timeoutMs = 3e4) {
10593
11099
  try {
10594
- const { stdout } = await execAsync7(command, { timeout: timeoutMs });
11100
+ const { stdout } = await execAsync8(command, { timeout: timeoutMs });
10595
11101
  const trimmed = stdout.trim();
10596
11102
  if (!trimmed || trimmed === "[]") {
10597
11103
  return [];
@@ -10811,9 +11317,9 @@ async function scanGcpResources(options) {
10811
11317
  }
10812
11318
 
10813
11319
  // src/services/terraform-import.service.ts
10814
- import { exec as exec8 } from "child_process";
10815
- import { promisify as promisify8 } from "util";
10816
- var execAsync8 = promisify8(exec8);
11320
+ import { exec as exec9 } from "child_process";
11321
+ import { promisify as promisify9 } from "util";
11322
+ var execAsync9 = promisify9(exec9);
10817
11323
  function toTerraformName(name) {
10818
11324
  return name.replace(/[^a-zA-Z0-9]/g, "-");
10819
11325
  }
@@ -10944,7 +11450,7 @@ async function importResource(conflict, config, stackDir) {
10944
11450
  const importId = mapping.importIdFormatter(conflict.resource, config);
10945
11451
  const tfAddress = mapping.terraformAddressFormatter(conflict.resource);
10946
11452
  try {
10947
- await execAsync8(`terraform import '${tfAddress}' '${importId}'`, {
11453
+ await execAsync9(`terraform import '${tfAddress}' '${importId}'`, {
10948
11454
  cwd: stackDir,
10949
11455
  timeout: 6e4
10950
11456
  });
@@ -10985,7 +11491,7 @@ function getImportCommand(resource, config) {
10985
11491
  }
10986
11492
 
10987
11493
  // src/services/preflight.service.ts
10988
- var execAsync9 = promisify9(exec9);
11494
+ var execAsync10 = promisify10(exec10);
10989
11495
  function groupByType(conflicts) {
10990
11496
  const groups = {};
10991
11497
  for (const conflict of conflicts) {
@@ -11139,7 +11645,7 @@ async function runKernelPreflightCheck(gcpProjectId, cwd = process.cwd()) {
11139
11645
  const errors = [];
11140
11646
  const warnings = [];
11141
11647
  try {
11142
- await execAsync9("docker info", { timeout: 1e4 });
11648
+ await execAsync10("docker info", { timeout: 1e4 });
11143
11649
  checks.push({
11144
11650
  name: "Docker",
11145
11651
  status: "pass",
@@ -11174,7 +11680,7 @@ async function runKernelPreflightCheck(gcpProjectId, cwd = process.cwd()) {
11174
11680
  }
11175
11681
  }
11176
11682
  try {
11177
- await execAsync9("gcloud --version", { timeout: 1e4 });
11683
+ await execAsync10("gcloud --version", { timeout: 1e4 });
11178
11684
  checks.push({
11179
11685
  name: "gcloud CLI",
11180
11686
  status: "pass",
@@ -11190,7 +11696,7 @@ async function runKernelPreflightCheck(gcpProjectId, cwd = process.cwd()) {
11190
11696
  errors.push("gcloud CLI is not installed");
11191
11697
  }
11192
11698
  try {
11193
- const { stdout } = await execAsync9('gcloud auth list --filter=status:ACTIVE --format="value(account)"', { timeout: 1e4 });
11699
+ const { stdout } = await execAsync10('gcloud auth list --filter=status:ACTIVE --format="value(account)"', { timeout: 1e4 });
11194
11700
  if (stdout.trim()) {
11195
11701
  checks.push({
11196
11702
  name: "GCP Authentication",
@@ -11278,7 +11784,7 @@ async function runKernelPreflightCheck(gcpProjectId, cwd = process.cwd()) {
11278
11784
  }
11279
11785
  if (gcpProjectId) {
11280
11786
  try {
11281
- const { stdout } = await execAsync9(
11787
+ const { stdout } = await execAsync10(
11282
11788
  `gcloud services list --project=${gcpProjectId} --format="value(config.name)" --filter="config.name:(run.googleapis.com OR cloudbuild.googleapis.com OR firestore.googleapis.com)"`,
11283
11789
  { timeout: 3e4 }
11284
11790
  );
@@ -11430,7 +11936,7 @@ async function ensureCloudFunctionsPrerequisites(gcpProjectId, region) {
11430
11936
  const errors = [];
11431
11937
  let projectNumber;
11432
11938
  try {
11433
- const { stdout } = await execAsync9(
11939
+ const { stdout } = await execAsync10(
11434
11940
  `gcloud projects describe ${gcpProjectId} --format="value(projectNumber)"`
11435
11941
  );
11436
11942
  projectNumber = stdout.trim();
@@ -11448,7 +11954,7 @@ async function ensureCloudFunctionsPrerequisites(gcpProjectId, region) {
11448
11954
  ];
11449
11955
  for (const api2 of requiredApis) {
11450
11956
  try {
11451
- await execAsync9(
11957
+ await execAsync10(
11452
11958
  `gcloud services enable ${api2} --project=${gcpProjectId} --quiet`,
11453
11959
  { timeout: 6e4 }
11454
11960
  );
@@ -11468,7 +11974,7 @@ async function ensureCloudFunctionsPrerequisites(gcpProjectId, region) {
11468
11974
  ];
11469
11975
  for (const role of cloudBuildRoles) {
11470
11976
  try {
11471
- await execAsync9(
11977
+ await execAsync10(
11472
11978
  `gcloud projects add-iam-policy-binding ${gcpProjectId} --member="serviceAccount:${cloudBuildSa}" --role="${role}" --condition=None --quiet`,
11473
11979
  { timeout: 3e4 }
11474
11980
  );
@@ -11484,7 +11990,7 @@ async function ensureCloudFunctionsPrerequisites(gcpProjectId, region) {
11484
11990
  ];
11485
11991
  for (const role of serverlessRoles) {
11486
11992
  try {
11487
- await execAsync9(
11993
+ await execAsync10(
11488
11994
  `gcloud projects add-iam-policy-binding ${gcpProjectId} --member="serviceAccount:${serverlessRobotSa}" --role="${role}" --condition=None --quiet`,
11489
11995
  { timeout: 3e4 }
11490
11996
  );
@@ -11499,7 +12005,7 @@ async function ensureCloudFunctionsPrerequisites(gcpProjectId, region) {
11499
12005
  ];
11500
12006
  for (const role of computeRoles) {
11501
12007
  try {
11502
- await execAsync9(
12008
+ await execAsync10(
11503
12009
  `gcloud projects add-iam-policy-binding ${gcpProjectId} --member="serviceAccount:${defaultComputeSa}" --role="${role}" --condition=None --quiet`,
11504
12010
  { timeout: 3e4 }
11505
12011
  );
@@ -11508,7 +12014,7 @@ async function ensureCloudFunctionsPrerequisites(gcpProjectId, region) {
11508
12014
  }
11509
12015
  }
11510
12016
  try {
11511
- await execAsync9(
12017
+ await execAsync10(
11512
12018
  `gcloud artifacts repositories describe gcf-artifacts --location=${region} --project=${gcpProjectId}`,
11513
12019
  { timeout: 3e4 }
11514
12020
  );
@@ -11517,7 +12023,7 @@ async function ensureCloudFunctionsPrerequisites(gcpProjectId, region) {
11517
12023
  for (const sa of serviceAccounts) {
11518
12024
  for (const role of repoRoles) {
11519
12025
  try {
11520
- await execAsync9(
12026
+ await execAsync10(
11521
12027
  `gcloud artifacts repositories add-iam-policy-binding gcf-artifacts --location=${region} --project=${gcpProjectId} --member="serviceAccount:${sa}" --role="${role}" --quiet`,
11522
12028
  { timeout: 3e4 }
11523
12029
  );
@@ -11528,7 +12034,7 @@ async function ensureCloudFunctionsPrerequisites(gcpProjectId, region) {
11528
12034
  }
11529
12035
  } catch {
11530
12036
  try {
11531
- await execAsync9(
12037
+ await execAsync10(
11532
12038
  `gcloud artifacts repositories create gcf-artifacts --repository-format=docker --location=${region} --project=${gcpProjectId} --description="Cloud Functions artifacts" --quiet`,
11533
12039
  { timeout: 6e4 }
11534
12040
  );
@@ -11538,7 +12044,7 @@ async function ensureCloudFunctionsPrerequisites(gcpProjectId, region) {
11538
12044
  for (const sa of serviceAccounts) {
11539
12045
  for (const role of repoRoles) {
11540
12046
  try {
11541
- await execAsync9(
12047
+ await execAsync10(
11542
12048
  `gcloud artifacts repositories add-iam-policy-binding gcf-artifacts --location=${region} --project=${gcpProjectId} --member="serviceAccount:${sa}" --role="${role}" --quiet`,
11543
12049
  { timeout: 3e4 }
11544
12050
  );
@@ -11560,7 +12066,7 @@ async function ensureCloudFunctionsPrerequisites(gcpProjectId, region) {
11560
12066
 
11561
12067
  // src/commands/infra/deploy.ts
11562
12068
  init_plugin_loader_service();
11563
- var execAsync10 = promisify10(exec10);
12069
+ var execAsync11 = promisify11(exec11);
11564
12070
  var STACKSOLO_DIR4 = ".stacksolo";
11565
12071
  function sleep(ms) {
11566
12072
  return new Promise((resolve7) => setTimeout(resolve7, ms));
@@ -12102,7 +12608,7 @@ async function runDeploy(options, retryCount = 0, retryContext = {}, sessionId)
12102
12608
  console.log(progress.next());
12103
12609
  const authSpinner = ora4("Configuring Docker authentication...").start();
12104
12610
  try {
12105
- await execAsync10(
12611
+ await execAsync11(
12106
12612
  `gcloud auth configure-docker ${config.project.region}-docker.pkg.dev --quiet`
12107
12613
  );
12108
12614
  authSpinner.succeed("Docker authentication configured");
@@ -12123,7 +12629,7 @@ async function runDeploy(options, retryCount = 0, retryContext = {}, sessionId)
12123
12629
  }
12124
12630
  const buildSpinner = ora4(`Building ${container.name}...`).start();
12125
12631
  try {
12126
- await execAsync10(`docker build -t "${imageTag}" "${sourceDir}"`, {
12632
+ await execAsync11(`docker build -t "${imageTag}" "${sourceDir}"`, {
12127
12633
  maxBuffer: 50 * 1024 * 1024
12128
12634
  });
12129
12635
  buildSpinner.succeed(`Built ${container.name}`);
@@ -12136,7 +12642,7 @@ async function runDeploy(options, retryCount = 0, retryContext = {}, sessionId)
12136
12642
  }
12137
12643
  const pushSpinner = ora4(`Pushing ${container.name}...`).start();
12138
12644
  try {
12139
- await execAsync10(`docker push "${imageTag}"`);
12645
+ await execAsync11(`docker push "${imageTag}"`);
12140
12646
  pushSpinner.succeed(`Pushed ${container.name}`);
12141
12647
  console.log(chalk5.gray(` ${imageTag}
12142
12648
  `));
@@ -12230,7 +12736,7 @@ async function runDeploy(options, retryCount = 0, retryContext = {}, sessionId)
12230
12736
  const namespace = k8sConfig.namespace || config.project.name;
12231
12737
  const releaseName = config.project.name;
12232
12738
  try {
12233
- await execAsync10(
12739
+ await execAsync11(
12234
12740
  `helm upgrade --install ${releaseName} ${helmOutputDir} --namespace ${namespace} --create-namespace`
12235
12741
  );
12236
12742
  console.log(chalk5.green("\n Deployed successfully with Helm!"));
@@ -12487,7 +12993,7 @@ async function runDeploy(options, retryCount = 0, retryContext = {}, sessionId)
12487
12993
  `));
12488
12994
  const spinner2 = ora4(`Enabling ${apiName}...`).start();
12489
12995
  try {
12490
- await execAsync10(`gcloud services enable ${apiName} --project=${config.project.gcpProjectId}`);
12996
+ await execAsync11(`gcloud services enable ${apiName} --project=${config.project.gcpProjectId}`);
12491
12997
  spinner2.succeed(`Enabled ${apiName}`);
12492
12998
  console.log(chalk5.gray(" Waiting 30 seconds for API to propagate...\n"));
12493
12999
  await sleep(3e4);
@@ -12698,11 +13204,11 @@ async function promptFixBuildPermissions() {
12698
13204
  }
12699
13205
  async function checkCloudBuildPermissions(gcpProjectId) {
12700
13206
  try {
12701
- const { stdout: projectNumber } = await execAsync10(
13207
+ const { stdout: projectNumber } = await execAsync11(
12702
13208
  `gcloud projects describe ${gcpProjectId} --format="value(projectNumber)"`
12703
13209
  );
12704
13210
  const trimmedProjectNumber = projectNumber.trim();
12705
- const { stdout: policy } = await execAsync10(
13211
+ const { stdout: policy } = await execAsync11(
12706
13212
  `gcloud projects get-iam-policy ${gcpProjectId} --format="json" --flatten="bindings[].members" --filter="bindings.members:${trimmedProjectNumber}@cloudbuild.gserviceaccount.com AND bindings.role:roles/storage.objectViewer"`
12707
13213
  );
12708
13214
  return policy.trim().length > 10;
@@ -12713,7 +13219,7 @@ async function checkCloudBuildPermissions(gcpProjectId) {
12713
13219
  async function grantCloudBuildPermissions(gcpProjectId, region) {
12714
13220
  const spinner = ora4("Getting project number...").start();
12715
13221
  try {
12716
- const { stdout: projectNumber } = await execAsync10(
13222
+ const { stdout: projectNumber } = await execAsync11(
12717
13223
  `gcloud projects describe ${gcpProjectId} --format="value(projectNumber)"`
12718
13224
  );
12719
13225
  const trimmedProjectNumber = projectNumber.trim();
@@ -12724,7 +13230,7 @@ async function grantCloudBuildPermissions(gcpProjectId, region) {
12724
13230
  ];
12725
13231
  spinner.text = "Granting permissions to Cloud Build service account...";
12726
13232
  for (const { role } of cloudBuildRoles) {
12727
- await execAsync10(
13233
+ await execAsync11(
12728
13234
  `gcloud projects add-iam-policy-binding ${gcpProjectId} --member="serviceAccount:${trimmedProjectNumber}@cloudbuild.gserviceaccount.com" --role="${role}" --quiet`
12729
13235
  );
12730
13236
  }
@@ -12735,13 +13241,13 @@ async function grantCloudBuildPermissions(gcpProjectId, region) {
12735
13241
  ];
12736
13242
  spinner.text = "Granting permissions to Cloud Functions service account...";
12737
13243
  for (const { role } of serverlessRobotRoles) {
12738
- await execAsync10(
13244
+ await execAsync11(
12739
13245
  `gcloud projects add-iam-policy-binding ${gcpProjectId} --member="serviceAccount:service-${trimmedProjectNumber}@serverless-robot-prod.iam.gserviceaccount.com" --role="${role}" --quiet`
12740
13246
  );
12741
13247
  }
12742
13248
  spinner.text = "Checking for gcf-artifacts repository...";
12743
13249
  try {
12744
- await execAsync10(`gcloud artifacts repositories describe gcf-artifacts --location=${region} --project=${gcpProjectId}`);
13250
+ await execAsync11(`gcloud artifacts repositories describe gcf-artifacts --location=${region} --project=${gcpProjectId}`);
12745
13251
  await grantGcfArtifactsPermissions(gcpProjectId, region);
12746
13252
  } catch {
12747
13253
  }
@@ -12759,7 +13265,7 @@ async function grantCloudBuildPermissions(gcpProjectId, region) {
12759
13265
  async function grantGcfArtifactsPermissions(gcpProjectId, region) {
12760
13266
  const spinner = ora4("Granting gcf-artifacts permissions...").start();
12761
13267
  try {
12762
- const { stdout: projectNumber } = await execAsync10(
13268
+ const { stdout: projectNumber } = await execAsync11(
12763
13269
  `gcloud projects describe ${gcpProjectId} --format="value(projectNumber)"`
12764
13270
  );
12765
13271
  const trimmedProjectNumber = projectNumber.trim();
@@ -12771,7 +13277,7 @@ async function grantGcfArtifactsPermissions(gcpProjectId, region) {
12771
13277
  spinner.text = "Granting artifactregistry.writer on gcf-artifacts...";
12772
13278
  for (const sa of serviceAccounts) {
12773
13279
  try {
12774
- await execAsync10(
13280
+ await execAsync11(
12775
13281
  `gcloud artifacts repositories add-iam-policy-binding gcf-artifacts --location=${region} --project=${gcpProjectId} --member="serviceAccount:${sa}" --role="roles/artifactregistry.writer" --quiet`
12776
13282
  );
12777
13283
  } catch {
@@ -12856,7 +13362,7 @@ async function forceDeleteResource(resource, gcpProjectId) {
12856
13362
  const regionMatch = fullPath.match(/locations\/([^/]+)/);
12857
13363
  const region = regionMatch ? regionMatch[1] : "us-east1";
12858
13364
  spinner.text = `Deleting Artifact Registry ${name}...`;
12859
- await execAsync10(
13365
+ await execAsync11(
12860
13366
  `gcloud artifacts repositories delete ${name} --location=${region} --project=${gcpProjectId} --quiet`
12861
13367
  );
12862
13368
  spinner.succeed(`Deleted Artifact Registry ${name}`);
@@ -12867,7 +13373,7 @@ async function forceDeleteResource(resource, gcpProjectId) {
12867
13373
  case "serviceaccount/Account": {
12868
13374
  spinner.text = `Deleting Service Account ${name}...`;
12869
13375
  const email = name.includes("@") ? name : `${name}@${gcpProjectId}.iam.gserviceaccount.com`;
12870
- await execAsync10(
13376
+ await execAsync11(
12871
13377
  `gcloud iam service-accounts delete ${email} --project=${gcpProjectId} --quiet`
12872
13378
  );
12873
13379
  spinner.succeed(`Deleted Service Account ${name}`);
@@ -12878,7 +13384,7 @@ async function forceDeleteResource(resource, gcpProjectId) {
12878
13384
  case "globalAddresses":
12879
13385
  case "compute/GlobalAddress": {
12880
13386
  spinner.text = `Deleting Global Address ${name}...`;
12881
- await execAsync10(
13387
+ await execAsync11(
12882
13388
  `gcloud compute addresses delete ${name} --global --project=${gcpProjectId} --quiet`
12883
13389
  );
12884
13390
  spinner.succeed(`Deleted Global Address ${name}`);
@@ -12896,7 +13402,7 @@ async function forceDeleteResource(resource, gcpProjectId) {
12896
13402
  }
12897
13403
  spinner.text = `Deleting URL Map ${name}...`;
12898
13404
  }
12899
- await execAsync10(
13405
+ await execAsync11(
12900
13406
  `gcloud compute url-maps delete ${name} --global --project=${gcpProjectId} --quiet`
12901
13407
  );
12902
13408
  spinner.succeed(`Deleted URL Map ${name}`);
@@ -12914,7 +13420,7 @@ async function forceDeleteResource(resource, gcpProjectId) {
12914
13420
  }
12915
13421
  spinner.text = `Deleting Target HTTP Proxy ${name}...`;
12916
13422
  }
12917
- await execAsync10(
13423
+ await execAsync11(
12918
13424
  `gcloud compute target-http-proxies delete ${name} --global --project=${gcpProjectId} --quiet`
12919
13425
  );
12920
13426
  spinner.succeed(`Deleted Target HTTP Proxy ${name}`);
@@ -12931,7 +13437,7 @@ async function forceDeleteResource(resource, gcpProjectId) {
12931
13437
  }
12932
13438
  spinner.text = `Deleting Target HTTPS Proxy ${name}...`;
12933
13439
  }
12934
- await execAsync10(
13440
+ await execAsync11(
12935
13441
  `gcloud compute target-https-proxies delete ${name} --global --project=${gcpProjectId} --quiet`
12936
13442
  );
12937
13443
  spinner.succeed(`Deleted Target HTTPS Proxy ${name}`);
@@ -12942,7 +13448,7 @@ async function forceDeleteResource(resource, gcpProjectId) {
12942
13448
  case "globalForwardingRules":
12943
13449
  case "compute/GlobalForwardingRule": {
12944
13450
  spinner.text = `Deleting Forwarding Rule ${name}...`;
12945
- await execAsync10(
13451
+ await execAsync11(
12946
13452
  `gcloud compute forwarding-rules delete ${name} --global --project=${gcpProjectId} --quiet`
12947
13453
  );
12948
13454
  spinner.succeed(`Deleted Forwarding Rule ${name}`);
@@ -12952,7 +13458,7 @@ async function forceDeleteResource(resource, gcpProjectId) {
12952
13458
  case "backendServices":
12953
13459
  case "compute/BackendService": {
12954
13460
  spinner.text = `Deleting Backend Service ${name}...`;
12955
- await execAsync10(
13461
+ await execAsync11(
12956
13462
  `gcloud compute backend-services delete ${name} --global --project=${gcpProjectId} --quiet`
12957
13463
  );
12958
13464
  spinner.succeed(`Deleted Backend Service ${name}`);
@@ -12962,7 +13468,7 @@ async function forceDeleteResource(resource, gcpProjectId) {
12962
13468
  case "backendBuckets":
12963
13469
  case "compute/BackendBucket": {
12964
13470
  spinner.text = `Deleting Backend Bucket ${name}...`;
12965
- await execAsync10(
13471
+ await execAsync11(
12966
13472
  `gcloud compute backend-buckets delete ${name} --project=${gcpProjectId} --quiet`
12967
13473
  );
12968
13474
  spinner.succeed(`Deleted Backend Bucket ${name}`);
@@ -12972,7 +13478,7 @@ async function forceDeleteResource(resource, gcpProjectId) {
12972
13478
  case "healthChecks":
12973
13479
  case "compute/HealthCheck": {
12974
13480
  spinner.text = `Deleting Health Check ${name}...`;
12975
- await execAsync10(
13481
+ await execAsync11(
12976
13482
  `gcloud compute health-checks delete ${name} --global --project=${gcpProjectId} --quiet`
12977
13483
  );
12978
13484
  spinner.succeed(`Deleted Health Check ${name}`);
@@ -12984,7 +13490,7 @@ async function forceDeleteResource(resource, gcpProjectId) {
12984
13490
  const regionMatch = fullPath.match(/regions\/([^/]+)/);
12985
13491
  const region = regionMatch ? regionMatch[1] : "us-east1";
12986
13492
  spinner.text = `Deleting Network Endpoint Group ${name}...`;
12987
- await execAsync10(
13493
+ await execAsync11(
12988
13494
  `gcloud compute network-endpoint-groups delete ${name} --region=${region} --project=${gcpProjectId} --quiet`
12989
13495
  );
12990
13496
  spinner.succeed(`Deleted Network Endpoint Group ${name}`);
@@ -12994,7 +13500,7 @@ async function forceDeleteResource(resource, gcpProjectId) {
12994
13500
  case "networks":
12995
13501
  case "compute/Network": {
12996
13502
  spinner.text = `Deleting VPC Network ${name}...`;
12997
- await execAsync10(
13503
+ await execAsync11(
12998
13504
  `gcloud compute networks delete ${name} --project=${gcpProjectId} --quiet`
12999
13505
  );
13000
13506
  spinner.succeed(`Deleted VPC Network ${name}`);
@@ -13006,7 +13512,7 @@ async function forceDeleteResource(resource, gcpProjectId) {
13006
13512
  const locationMatch = fullPath.match(/locations\/([^/]+)/);
13007
13513
  const location = locationMatch ? locationMatch[1] : "us-east1";
13008
13514
  spinner.text = `Deleting Cloud Function ${name}...`;
13009
- await execAsync10(
13515
+ await execAsync11(
13010
13516
  `gcloud functions delete ${name} --region=${location} --project=${gcpProjectId} --gen2 --quiet`
13011
13517
  );
13012
13518
  spinner.succeed(`Deleted Cloud Function ${name}`);
@@ -13018,7 +13524,7 @@ async function forceDeleteResource(resource, gcpProjectId) {
13018
13524
  const locationMatch = fullPath.match(/locations\/([^/]+)/);
13019
13525
  const location = locationMatch ? locationMatch[1] : "us-east1";
13020
13526
  spinner.text = `Deleting Cloud Run service ${name}...`;
13021
- await execAsync10(
13527
+ await execAsync11(
13022
13528
  `gcloud run services delete ${name} --region=${location} --project=${gcpProjectId} --quiet`
13023
13529
  );
13024
13530
  spinner.succeed(`Deleted Cloud Run service ${name}`);
@@ -13028,7 +13534,7 @@ async function forceDeleteResource(resource, gcpProjectId) {
13028
13534
  case "topics":
13029
13535
  case "pubsub/Topic": {
13030
13536
  spinner.text = `Deleting Pub/Sub topic ${name}...`;
13031
- await execAsync10(`gcloud pubsub topics delete ${name} --project=${gcpProjectId} --quiet`);
13537
+ await execAsync11(`gcloud pubsub topics delete ${name} --project=${gcpProjectId} --quiet`);
13032
13538
  spinner.succeed(`Deleted Pub/Sub topic ${name}`);
13033
13539
  return true;
13034
13540
  }
@@ -13044,7 +13550,7 @@ async function forceDeleteResource(resource, gcpProjectId) {
13044
13550
  }
13045
13551
  spinner.text = `Deleting SSL Certificate ${name}...`;
13046
13552
  }
13047
- await execAsync10(
13553
+ await execAsync11(
13048
13554
  `gcloud compute ssl-certificates delete ${name} --global --project=${gcpProjectId} --quiet`
13049
13555
  );
13050
13556
  spinner.succeed(`Deleted SSL Certificate ${name}`);
@@ -13054,7 +13560,7 @@ async function forceDeleteResource(resource, gcpProjectId) {
13054
13560
  case "buckets":
13055
13561
  case "storage/Bucket": {
13056
13562
  spinner.text = `Deleting Storage Bucket ${name}...`;
13057
- await execAsync10(`gcloud storage rm -r gs://${name} --project=${gcpProjectId}`);
13563
+ await execAsync11(`gcloud storage rm -r gs://${name} --project=${gcpProjectId}`);
13058
13564
  spinner.succeed(`Deleted Storage Bucket ${name}`);
13059
13565
  return true;
13060
13566
  }
@@ -13064,7 +13570,7 @@ async function forceDeleteResource(resource, gcpProjectId) {
13064
13570
  const regionMatch = fullPath.match(/locations\/([^/]+)/);
13065
13571
  const region = regionMatch ? regionMatch[1] : "us-east1";
13066
13572
  spinner.text = `Deleting VPC Connector ${name}...`;
13067
- await execAsync10(
13573
+ await execAsync11(
13068
13574
  `gcloud compute networks vpc-access connectors delete ${name} --region=${region} --project=${gcpProjectId} --quiet`
13069
13575
  );
13070
13576
  spinner.succeed(`Deleted VPC Connector ${name}`);
@@ -13147,7 +13653,7 @@ async function findDependentResources(resourceName, resourceType, gcpProjectId)
13147
13653
  const dependencies = [];
13148
13654
  try {
13149
13655
  if (resourceType === "url-map") {
13150
- const { stdout: httpProxies } = await execAsync10(
13656
+ const { stdout: httpProxies } = await execAsync11(
13151
13657
  `gcloud compute target-http-proxies list --project=${gcpProjectId} --format="json" 2>/dev/null || echo "[]"`
13152
13658
  );
13153
13659
  const proxies = JSON.parse(httpProxies);
@@ -13160,7 +13666,7 @@ async function findDependentResources(resourceName, resourceType, gcpProjectId)
13160
13666
  });
13161
13667
  }
13162
13668
  }
13163
- const { stdout: httpsProxies } = await execAsync10(
13669
+ const { stdout: httpsProxies } = await execAsync11(
13164
13670
  `gcloud compute target-https-proxies list --project=${gcpProjectId} --format="json" 2>/dev/null || echo "[]"`
13165
13671
  );
13166
13672
  const httpsProxyList = JSON.parse(httpsProxies);
@@ -13174,7 +13680,7 @@ async function findDependentResources(resourceName, resourceType, gcpProjectId)
13174
13680
  }
13175
13681
  }
13176
13682
  } else if (resourceType === "http-proxy" || resourceType === "https-proxy") {
13177
- const { stdout: rules } = await execAsync10(
13683
+ const { stdout: rules } = await execAsync11(
13178
13684
  `gcloud compute forwarding-rules list --global --project=${gcpProjectId} --format="json" 2>/dev/null || echo "[]"`
13179
13685
  );
13180
13686
  const ruleList = JSON.parse(rules);
@@ -13188,7 +13694,7 @@ async function findDependentResources(resourceName, resourceType, gcpProjectId)
13188
13694
  }
13189
13695
  }
13190
13696
  } else if (resourceType === "ssl-cert") {
13191
- const { stdout: httpsProxies } = await execAsync10(
13697
+ const { stdout: httpsProxies } = await execAsync11(
13192
13698
  `gcloud compute target-https-proxies list --project=${gcpProjectId} --format="json" 2>/dev/null || echo "[]"`
13193
13699
  );
13194
13700
  const proxyList = JSON.parse(httpsProxies);
@@ -13223,7 +13729,7 @@ async function grantResourceDeletePermissions(resourcePath, gcpProjectId) {
13223
13729
  const inquirer6 = await import("inquirer");
13224
13730
  let currentAccount = "";
13225
13731
  try {
13226
- const { stdout } = await execAsync10("gcloud config get-value account");
13732
+ const { stdout } = await execAsync11("gcloud config get-value account");
13227
13733
  currentAccount = stdout.trim();
13228
13734
  } catch {
13229
13735
  }
@@ -13273,7 +13779,7 @@ async function grantResourceDeletePermissions(resourcePath, gcpProjectId) {
13273
13779
  });
13274
13780
  child.on("error", reject);
13275
13781
  });
13276
- const { stdout } = await execAsync10("gcloud config get-value account");
13782
+ const { stdout } = await execAsync11("gcloud config get-value account");
13277
13783
  email = stdout.trim();
13278
13784
  console.log(chalk5.green(`
13279
13785
  Now authenticated as: ${email}
@@ -13323,7 +13829,7 @@ async function grantResourceDeletePermissions(resourcePath, gcpProjectId) {
13323
13829
  roleName = "Editor";
13324
13830
  }
13325
13831
  spinner.text = `Granting ${roleName} role to ${email}...`;
13326
- await execAsync10(
13832
+ await execAsync11(
13327
13833
  `gcloud projects add-iam-policy-binding ${gcpProjectId} --member="user:${email}" --role="${role}" --quiet`
13328
13834
  );
13329
13835
  spinner.succeed(`Granted ${roleName} role to ${email}`);
@@ -14388,9 +14894,9 @@ eventsCommand.action(async () => {
14388
14894
  // src/commands/infra/inventory.ts
14389
14895
  import { Command as Command13 } from "commander";
14390
14896
  import chalk14 from "chalk";
14391
- import { exec as exec11 } from "child_process";
14392
- import { promisify as promisify11 } from "util";
14393
- var execAsync11 = promisify11(exec11);
14897
+ import { exec as exec12 } from "child_process";
14898
+ import { promisify as promisify12 } from "util";
14899
+ var execAsync12 = promisify12(exec12);
14394
14900
  var LABEL_UPDATE_COMMANDS = {
14395
14901
  "VPC Network": (projectId, name, labels) => {
14396
14902
  const labelStr = Object.entries(labels).map(([k, v]) => `${k}=${v}`).join(",");
@@ -14431,7 +14937,7 @@ async function updateResourceLabels(projectId, resourceType, resourceName, label
14431
14937
  command = command.replace(/us-central1/g, region);
14432
14938
  }
14433
14939
  try {
14434
- await execAsync11(command, { timeout: 6e4 });
14940
+ await execAsync12(command, { timeout: 6e4 });
14435
14941
  return { success: true };
14436
14942
  } catch (err) {
14437
14943
  const errorMsg = err instanceof Error ? err.message : String(err);
@@ -14461,7 +14967,7 @@ async function shareResource(projectId, resourceType, resourceName, existingLabe
14461
14967
  }
14462
14968
  async function runGcloudCommand2(command, timeoutMs = 3e4) {
14463
14969
  try {
14464
- const { stdout } = await execAsync11(command, { timeout: timeoutMs });
14970
+ const { stdout } = await execAsync12(command, { timeout: timeoutMs });
14465
14971
  const trimmed = stdout.trim();
14466
14972
  if (!trimmed || trimmed === "[]") {
14467
14973
  return [];
@@ -14671,7 +15177,7 @@ async function getGcpProjectId(explicitProject) {
14671
15177
  } catch {
14672
15178
  }
14673
15179
  try {
14674
- const { stdout } = await execAsync11("gcloud config get-value project 2>/dev/null");
15180
+ const { stdout } = await execAsync12("gcloud config get-value project 2>/dev/null");
14675
15181
  const gcloudProject = stdout.trim();
14676
15182
  if (gcloudProject && gcloudProject !== "(unset)") {
14677
15183
  return gcloudProject;
@@ -14851,9 +15357,9 @@ import { Command as Command14 } from "commander";
14851
15357
  import chalk15 from "chalk";
14852
15358
  import * as path19 from "path";
14853
15359
  import * as fs16 from "fs/promises";
14854
- import { exec as exec12 } from "child_process";
14855
- import { promisify as promisify12 } from "util";
14856
- var execAsync12 = promisify12(exec12);
15360
+ import { exec as exec13 } from "child_process";
15361
+ import { promisify as promisify13 } from "util";
15362
+ var execAsync13 = promisify13(exec13);
14857
15363
  var STACKSOLO_DIR7 = ".stacksolo";
14858
15364
  var CONFIG_FILENAME5 = "stacksolo.config.json";
14859
15365
  var doctorCommand = new Command14("doctor").description("Check system prerequisites and GCP configuration").option("--fix", "Attempt to fix issues automatically").option("--verbose", "Show detailed output for each check").action(async (options) => {
@@ -14931,7 +15437,7 @@ function displayResult(result, verbose) {
14931
15437
  }
14932
15438
  async function checkNode(_options) {
14933
15439
  try {
14934
- const { stdout } = await execAsync12("node --version");
15440
+ const { stdout } = await execAsync13("node --version");
14935
15441
  const version = stdout.trim().replace("v", "");
14936
15442
  const major = parseInt(version.split(".")[0], 10);
14937
15443
  if (major >= 18) {
@@ -14966,7 +15472,7 @@ async function checkNode(_options) {
14966
15472
  }
14967
15473
  async function checkTerraform(_options) {
14968
15474
  try {
14969
- const { stdout } = await execAsync12("terraform version -json");
15475
+ const { stdout } = await execAsync13("terraform version -json");
14970
15476
  const data = JSON.parse(stdout);
14971
15477
  const version = data.terraform_version;
14972
15478
  return {
@@ -14976,7 +15482,7 @@ async function checkTerraform(_options) {
14976
15482
  };
14977
15483
  } catch {
14978
15484
  try {
14979
- const { stdout } = await execAsync12("terraform version");
15485
+ const { stdout } = await execAsync13("terraform version");
14980
15486
  const match = stdout.match(/Terraform v(\d+\.\d+\.\d+)/);
14981
15487
  if (match) {
14982
15488
  return {
@@ -14997,11 +15503,11 @@ async function checkTerraform(_options) {
14997
15503
  }
14998
15504
  async function checkDocker(_options) {
14999
15505
  try {
15000
- const { stdout } = await execAsync12("docker --version");
15506
+ const { stdout } = await execAsync13("docker --version");
15001
15507
  const match = stdout.match(/Docker version (\d+\.\d+\.\d+)/);
15002
15508
  const version = match ? match[1] : "unknown";
15003
15509
  try {
15004
- await execAsync12("docker info", { timeout: 5e3 });
15510
+ await execAsync13("docker info", { timeout: 5e3 });
15005
15511
  return {
15006
15512
  name: "Docker",
15007
15513
  status: "ok",
@@ -15026,7 +15532,7 @@ async function checkDocker(_options) {
15026
15532
  }
15027
15533
  async function checkGcloud(_options) {
15028
15534
  try {
15029
- const { stdout } = await execAsync12("gcloud version --format=json");
15535
+ const { stdout } = await execAsync13("gcloud version --format=json");
15030
15536
  const data = JSON.parse(stdout);
15031
15537
  const version = data["Google Cloud SDK"];
15032
15538
  return {
@@ -15045,7 +15551,7 @@ async function checkGcloud(_options) {
15045
15551
  }
15046
15552
  async function checkGcpAuth(_options) {
15047
15553
  try {
15048
- const { stdout } = await execAsync12("gcloud auth list --format=json");
15554
+ const { stdout } = await execAsync13("gcloud auth list --format=json");
15049
15555
  const accounts = JSON.parse(stdout);
15050
15556
  if (accounts.length === 0) {
15051
15557
  return {
@@ -15065,7 +15571,7 @@ async function checkGcpAuth(_options) {
15065
15571
  };
15066
15572
  }
15067
15573
  try {
15068
- await execAsync12("gcloud auth application-default print-access-token", { timeout: 5e3 });
15574
+ await execAsync13("gcloud auth application-default print-access-token", { timeout: 5e3 });
15069
15575
  return {
15070
15576
  name: "GCP Authentication",
15071
15577
  status: "ok",
@@ -15121,7 +15627,7 @@ async function checkGcpProjectAccess(_options) {
15121
15627
  try {
15122
15628
  const config = parseConfig(configPath);
15123
15629
  const projectId = config.project.gcpProjectId;
15124
- const { stdout } = await execAsync12(
15630
+ const { stdout } = await execAsync13(
15125
15631
  `gcloud projects describe ${projectId} --format="value(projectId)"`,
15126
15632
  { timeout: 1e4 }
15127
15633
  );
@@ -15162,7 +15668,7 @@ async function checkRequiredApis(_options) {
15162
15668
  "cloudbuild.googleapis.com",
15163
15669
  "artifactregistry.googleapis.com"
15164
15670
  ];
15165
- const { stdout } = await execAsync12(
15671
+ const { stdout } = await execAsync13(
15166
15672
  `gcloud services list --project=${projectId} --enabled --format="value(NAME)"`,
15167
15673
  { timeout: 3e4 }
15168
15674
  );
@@ -15390,9 +15896,9 @@ import chalk17 from "chalk";
15390
15896
  import ora7 from "ora";
15391
15897
  import * as path21 from "path";
15392
15898
  import * as fs18 from "fs/promises";
15393
- import { exec as exec13 } from "child_process";
15394
- import { promisify as promisify13 } from "util";
15395
- var execAsync13 = promisify13(exec13);
15899
+ import { exec as exec14 } from "child_process";
15900
+ import { promisify as promisify14 } from "util";
15901
+ var execAsync14 = promisify14(exec14);
15396
15902
  var STACKSOLO_DIR9 = ".stacksolo";
15397
15903
  var CONFIG_FILENAME7 = "stacksolo.config.json";
15398
15904
  function getConfigPath5() {
@@ -15414,7 +15920,7 @@ var buildCommand = new Command16("build").description("Build and push container
15414
15920
  return;
15415
15921
  }
15416
15922
  try {
15417
- await execAsync13("docker --version");
15923
+ await execAsync14("docker --version");
15418
15924
  } catch {
15419
15925
  console.log(chalk17.red(" Docker CLI not found.\n"));
15420
15926
  console.log(chalk17.gray(" Install Docker Desktop: https://www.docker.com/products/docker-desktop\n"));
@@ -15453,7 +15959,7 @@ var buildCommand = new Command16("build").description("Build and push container
15453
15959
  if (options.push) {
15454
15960
  const authSpinner = ora7("Configuring Docker authentication...").start();
15455
15961
  try {
15456
- await execAsync13(
15962
+ await execAsync14(
15457
15963
  `gcloud auth configure-docker ${config.project.region}-docker.pkg.dev --quiet`
15458
15964
  );
15459
15965
  authSpinner.succeed("Docker authentication configured");
@@ -15482,7 +15988,7 @@ var buildCommand = new Command16("build").description("Build and push container
15482
15988
  const buildSpinner = ora7(`Building ${container.name}...`).start();
15483
15989
  try {
15484
15990
  const buildCmd = `docker build -f "${dockerfilePath}" -t "${imageTag}" "${sourceDir}"`;
15485
- await execAsync13(buildCmd, { maxBuffer: 50 * 1024 * 1024 });
15991
+ await execAsync14(buildCmd, { maxBuffer: 50 * 1024 * 1024 });
15486
15992
  buildSpinner.succeed(`Built ${container.name}`);
15487
15993
  } catch (error) {
15488
15994
  buildSpinner.fail(`Failed to build ${container.name}`);
@@ -15494,7 +16000,7 @@ var buildCommand = new Command16("build").description("Build and push container
15494
16000
  if (options.push) {
15495
16001
  const pushSpinner = ora7(`Pushing ${container.name}...`).start();
15496
16002
  try {
15497
- await execAsync13(`docker push "${imageTag}"`);
16003
+ await execAsync14(`docker push "${imageTag}"`);
15498
16004
  pushSpinner.succeed(`Pushed ${container.name}`);
15499
16005
  console.log(chalk17.gray(` ${imageTag}
15500
16006
  `));
@@ -15665,35 +16171,6 @@ function getPythonVersion(runtime) {
15665
16171
  if (version === "312") return "3.12";
15666
16172
  return "3.12";
15667
16173
  }
15668
- function getFrameworkConfig(framework, port = 3e3) {
15669
- switch (framework) {
15670
- case "vue":
15671
- return {
15672
- command: ["npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", String(port)]
15673
- };
15674
- case "nuxt":
15675
- return {
15676
- command: ["npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", String(port)]
15677
- };
15678
- case "react":
15679
- return {
15680
- command: ["npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", String(port)]
15681
- };
15682
- case "next":
15683
- return {
15684
- command: ["npm", "run", "dev", "--", "--hostname", "0.0.0.0", "-p", String(port)]
15685
- };
15686
- case "svelte":
15687
- case "sveltekit":
15688
- return {
15689
- command: ["npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", String(port)]
15690
- };
15691
- default:
15692
- return {
15693
- command: ["npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", String(port)]
15694
- };
15695
- }
15696
- }
15697
16174
  function isPythonRuntime(runtime) {
15698
16175
  return runtime.startsWith("python");
15699
16176
  }
@@ -15704,31 +16181,27 @@ function generateFunctionManifests(options) {
15704
16181
  const functionName = sanitizeName(options.function.name);
15705
16182
  const runtimeConfig = getRuntimeConfig(options.function.runtime, options.function.entryPoint);
15706
16183
  const isPython = isPythonRuntime(options.function.runtime);
15707
- const runCmd = isPython ? runtimeConfig.command.join(" ") : "npm run dev 2>/dev/null || " + runtimeConfig.command.join(" ");
15708
- const containerCommand = isPython ? [
15709
- "sh",
15710
- "-c",
15711
- [
15712
- "cp -r /source/* /app/ 2>/dev/null || true",
15713
- "cd /app",
15714
- "pip install -r requirements.txt 2>/dev/null || true",
15715
- "pip install functions-framework",
15716
- runCmd
15717
- ].join(" && ")
15718
- ] : [
15719
- "sh",
15720
- "-c",
15721
- [
15722
- // Copy source files to /app, excluding node_modules (macOS binaries don't work in Linux)
15723
- "cd /source",
15724
- "find . -maxdepth 1 ! -name node_modules ! -name . -exec cp -r {} /app/ \\;",
15725
- "cd /app",
15726
- // Always install fresh for Linux platform
15727
- "npm install",
15728
- // Run the dev server
15729
- runCmd
15730
- ].join(" && ")
15731
- ];
16184
+ let containerCommand;
16185
+ let containerImage;
16186
+ if (isPython) {
16187
+ containerImage = runtimeConfig.image;
16188
+ containerCommand = [
16189
+ "sh",
16190
+ "-c",
16191
+ [
16192
+ "cp -r /source/* /app/ 2>/dev/null || true",
16193
+ "cd /app",
16194
+ "pip install -r requirements.txt 2>/dev/null || true",
16195
+ "pip install functions-framework",
16196
+ runtimeConfig.command.join(" ")
16197
+ ].join(" && ")
16198
+ ];
16199
+ } else {
16200
+ containerImage = "node:20-slim";
16201
+ const functionsFrameworkCmd = runtimeConfig.command.join(" ");
16202
+ const command = 'cd /source && if [ -d dist ]; then echo "Running pre-built function from dist/" && find . -maxdepth 1 ! -name node_modules ! -name . -exec cp -r {} /app/ \\; && cd /app && grep -v "workspace:" package.json > package.json.tmp && mv package.json.tmp package.json && npm install --omit=dev && ' + functionsFrameworkCmd + '; else echo "No dist/ folder. Trying dev mode..." && find . -maxdepth 1 ! -name node_modules ! -name . -exec cp -r {} /app/ \\; && cd /app && npm install && (npm run dev 2>/dev/null || ' + functionsFrameworkCmd + "); fi";
16203
+ containerCommand = ["sh", "-c", command];
16204
+ }
15732
16205
  const labels = {
15733
16206
  "app.kubernetes.io/name": functionName,
15734
16207
  "app.kubernetes.io/component": "function",
@@ -15761,7 +16234,7 @@ function generateFunctionManifests(options) {
15761
16234
  containers: [
15762
16235
  {
15763
16236
  name: functionName,
15764
- image: runtimeConfig.image,
16237
+ image: containerImage,
15765
16238
  command: containerCommand,
15766
16239
  ports: [
15767
16240
  {
@@ -15858,7 +16331,7 @@ function sanitizeName(name) {
15858
16331
  function generateUIManifests(options) {
15859
16332
  const namespace = sanitizeNamespaceName(options.projectName);
15860
16333
  const uiName = sanitizeName2(options.ui.name);
15861
- const frameworkConfig = getFrameworkConfig(options.ui.framework, options.port);
16334
+ const command = 'if [ -d /source/dist ]; then echo "Serving pre-built static files from dist/" && npx serve /source/dist -l ' + options.port + ' -s; else echo "No dist/ folder found. Running dev server..." && cd /source && npm install && npm run dev -- --host 0.0.0.0 --port ' + options.port + "; fi";
15862
16335
  const labels = {
15863
16336
  "app.kubernetes.io/name": uiName,
15864
16337
  "app.kubernetes.io/component": "ui",
@@ -15892,21 +16365,7 @@ function generateUIManifests(options) {
15892
16365
  {
15893
16366
  name: uiName,
15894
16367
  image: "node:20-slim",
15895
- // Copy source (excluding node_modules) to working directory, then install fresh Linux deps
15896
- command: [
15897
- "sh",
15898
- "-c",
15899
- [
15900
- // Copy source files to /app, excluding node_modules (macOS binaries don't work in Linux)
15901
- "cd /source",
15902
- "find . -maxdepth 1 ! -name node_modules ! -name . -exec cp -r {} /app/ \\;",
15903
- "cd /app",
15904
- // Always install fresh for Linux platform
15905
- "npm install",
15906
- // Run the dev server
15907
- frameworkConfig.command.join(" ")
15908
- ].join(" && ")
15909
- ],
16368
+ command: ["sh", "-c", command],
15910
16369
  ports: [
15911
16370
  {
15912
16371
  containerPort: options.port,
@@ -16783,6 +17242,7 @@ function generateK8sManifests(options) {
16783
17242
  const services = [];
16784
17243
  const warnings = [];
16785
17244
  const projectName = config.project.name;
17245
+ const packageManager = config.project.packageManager;
16786
17246
  const portAllocator = createPortAllocator();
16787
17247
  const servicePortMap = {};
16788
17248
  let kernelUrl;
@@ -16845,7 +17305,8 @@ function generateK8sManifests(options) {
16845
17305
  timeout: func.timeout
16846
17306
  },
16847
17307
  sourceDir,
16848
- port
17308
+ port,
17309
+ packageManager
16849
17310
  });
16850
17311
  manifests.push(manifest);
16851
17312
  services.push(func.name);
@@ -16866,7 +17327,8 @@ function generateK8sManifests(options) {
16866
17327
  framework: ui.framework || "vue"
16867
17328
  },
16868
17329
  sourceDir,
16869
- port
17330
+ port,
17331
+ packageManager
16870
17332
  });
16871
17333
  manifests.push(manifest);
16872
17334
  services.push(ui.name);
@@ -17034,6 +17496,7 @@ function spawnService(service, manager, firebaseProjectId) {
17034
17496
  };
17035
17497
  if (firebaseProjectId) {
17036
17498
  env.FIREBASE_PROJECT_ID = firebaseProjectId;
17499
+ env.VITE_FIREBASE_PROJECT_ID = firebaseProjectId;
17037
17500
  }
17038
17501
  const args = service.type === "ui" ? ["run", "dev", "--", "--port", String(service.port)] : ["run", "dev"];
17039
17502
  const proc = spawn6("npm", args, {
@@ -17062,12 +17525,12 @@ function spawnService(service, manager, firebaseProjectId) {
17062
17525
  });
17063
17526
  return proc;
17064
17527
  }
17065
- async function startFirebaseEmulators(manager) {
17528
+ async function startFirebaseEmulators(manager, projectId = "demo-local") {
17066
17529
  const spinner = ora8("Starting Firebase emulators...").start();
17067
17530
  try {
17068
17531
  const proc = spawn6(
17069
17532
  "firebase",
17070
- ["emulators:start", "--only", "firestore,auth", "--project", "demo-local"],
17533
+ ["emulators:start", "--only", "firestore,auth", "--project", projectId],
17071
17534
  {
17072
17535
  stdio: ["ignore", "pipe", "pipe"],
17073
17536
  shell: true
@@ -17172,13 +17635,13 @@ async function startLocalEnvironment(options) {
17172
17635
  services: validServices,
17173
17636
  isShuttingDown: false
17174
17637
  };
17638
+ const firebaseProjectId = config.project.gcpKernel?.firebaseProjectId || config.project.gcpProjectId;
17175
17639
  if (options.includeEmulators !== false) {
17176
- const emulatorProc = await startFirebaseEmulators(manager);
17640
+ const emulatorProc = await startFirebaseEmulators(manager, firebaseProjectId);
17177
17641
  if (emulatorProc) {
17178
17642
  manager.processes.set("firebase-emulator", emulatorProc);
17179
17643
  }
17180
17644
  }
17181
- const firebaseProjectId = config.project.gcpKernel?.firebaseProjectId || config.project.gcpProjectId;
17182
17645
  const spinner = ora8("Starting services...").start();
17183
17646
  for (const service of validServices) {
17184
17647
  const proc = spawnService(service, manager, firebaseProjectId);
@@ -18212,9 +18675,9 @@ import chalk20 from "chalk";
18212
18675
  import ora10 from "ora";
18213
18676
  import * as path24 from "path";
18214
18677
  import * as fs21 from "fs/promises";
18215
- import { exec as exec14 } from "child_process";
18216
- import { promisify as promisify14 } from "util";
18217
- var execAsync14 = promisify14(exec14);
18678
+ import { exec as exec15 } from "child_process";
18679
+ import { promisify as promisify15 } from "util";
18680
+ var execAsync15 = promisify15(exec15);
18218
18681
  var STACKSOLO_DIR10 = ".stacksolo";
18219
18682
  var CONFIG_FILENAME8 = "stacksolo.config.json";
18220
18683
  var installCommand = new Command18("install").description("Install dependencies for all resources").option("-p, --parallel", "Install dependencies in parallel").action(async (options) => {
@@ -18278,7 +18741,7 @@ var installCommand = new Command18("install").description("Install dependencies
18278
18741
  const installDir = async (dir) => {
18279
18742
  const spinner = ora10(`Installing ${dir.name}...`).start();
18280
18743
  try {
18281
- await execAsync14("npm install", { cwd: dir.path, timeout: 12e4 });
18744
+ await execAsync15("npm install", { cwd: dir.path, timeout: 12e4 });
18282
18745
  spinner.succeed(`Installed ${dir.name}`);
18283
18746
  return { success: true, name: dir.name };
18284
18747
  } catch (error) {