@pulumix/core 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/dev/env-compute.d.ts +15 -0
  2. package/dist/dev/env-compute.d.ts.map +1 -0
  3. package/dist/dev/env-compute.js +92 -0
  4. package/dist/dev/env-compute.js.map +1 -0
  5. package/dist/dev/index.d.ts +1 -9
  6. package/dist/dev/index.d.ts.map +1 -1
  7. package/dist/dev/index.js +91 -211
  8. package/dist/dev/index.js.map +1 -1
  9. package/dist/dev/local-runner.d.ts.map +1 -1
  10. package/dist/dev/local-runner.js +0 -1
  11. package/dist/dev/local-runner.js.map +1 -1
  12. package/dist/dev/port-forward.d.ts +1 -1
  13. package/dist/dev/port-forward.d.ts.map +1 -1
  14. package/dist/dev/port-forward.js +43 -28
  15. package/dist/dev/port-forward.js.map +1 -1
  16. package/dist/dev/types.d.ts +4 -22
  17. package/dist/dev/types.d.ts.map +1 -1
  18. package/dist/dev/types.js +1 -24
  19. package/dist/dev/types.js.map +1 -1
  20. package/dist/index.d.ts +1 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +1 -0
  23. package/dist/index.js.map +1 -1
  24. package/dist/k8s/client.d.ts +8 -0
  25. package/dist/k8s/client.d.ts.map +1 -0
  26. package/dist/k8s/client.js +48 -0
  27. package/dist/k8s/client.js.map +1 -0
  28. package/dist/k8s/index.d.ts +5 -0
  29. package/dist/k8s/index.d.ts.map +1 -0
  30. package/dist/k8s/index.js +12 -0
  31. package/dist/k8s/index.js.map +1 -0
  32. package/dist/k8s/inspect.d.ts +40 -0
  33. package/dist/k8s/inspect.d.ts.map +1 -0
  34. package/dist/k8s/inspect.js +133 -0
  35. package/dist/k8s/inspect.js.map +1 -0
  36. package/dist/orchestrator/index.d.ts +10 -0
  37. package/dist/orchestrator/index.d.ts.map +1 -1
  38. package/dist/orchestrator/index.js +247 -167
  39. package/dist/orchestrator/index.js.map +1 -1
  40. package/package.json +3 -2
@@ -65,9 +65,10 @@ const resolveBackendUrl = (rootPath, projectConfig, stackName) => {
65
65
  switch (backend.type) {
66
66
  case 'file': {
67
67
  const backendPath = backend.path ?? 'dist/';
68
- const absolutePath = path.isAbsolute(backendPath)
68
+ const base = path.isAbsolute(backendPath)
69
69
  ? backendPath
70
70
  : path.join(rootPath, backendPath);
71
+ const absolutePath = path.join(base, stackName);
71
72
  return `file://${absolutePath}`;
72
73
  }
73
74
  case 's3': {
@@ -90,7 +91,7 @@ const resolveBackendUrl = (rootPath, projectConfig, stackName) => {
90
91
  return 'https://app.pulumi.com';
91
92
  }
92
93
  default: {
93
- const defaultPath = path.join(rootPath, 'dist/');
94
+ const defaultPath = path.join(rootPath, 'dist/', stackName);
94
95
  return `file://${defaultPath}`;
95
96
  }
96
97
  }
@@ -100,11 +101,12 @@ const resolveWorkDir = (rootPath, projectConfig, stackName) => {
100
101
  const backend = stackConfig?.backend ?? projectConfig.backend ?? DEFAULT_BACKEND;
101
102
  if (backend.type === 'file') {
102
103
  const backendPath = backend.path ?? 'dist/';
103
- return path.isAbsolute(backendPath)
104
+ const base = path.isAbsolute(backendPath)
104
105
  ? backendPath
105
106
  : path.join(rootPath, backendPath);
107
+ return path.join(base, stackName);
106
108
  }
107
- return path.join(rootPath, 'dist/');
109
+ return path.join(rootPath, 'dist/', stackName);
108
110
  };
109
111
  const parseYamlFile = (filePath) => {
110
112
  try {
@@ -444,150 +446,187 @@ class Orchestrator {
444
446
  }
445
447
  return (0, Either_1.Right)(undefined);
446
448
  }
447
- deploy(config) {
448
- const startTime = Date.now();
449
- return (0, EitherAsync_1.EitherAsync)(async ({ liftEither, throwE }) => {
450
- await liftEither(this.validateEnvironment(config.stackName));
451
- this.eventEmitter.emitPhaseStart('configuration');
452
- const rootConfigPath = path.join(config.rootPath, 'pulumix.yaml');
453
- const rawConfig = await liftEither(parseYamlFile(rootConfigPath));
454
- const rootConfig = await liftEither((0, project_1.validateProjectConfig)(rawConfig, rootConfigPath));
455
- const stacks = rootConfig.stacks ?? {};
456
- const globalConfig = stacks[config.stackName] ?? {};
457
- this.eventEmitter.emitPhaseComplete('configuration');
458
- this.eventEmitter.emitPhaseStart('discovery');
459
- const localServices = await liftEither(await (0, exports.discoverServices)(config.rootPath));
460
- const allowlist = rootConfig.services?.allowed ?? [];
461
- const publishedServices = await liftEither(await (0, exports.discoverPublishedServices)(config.rootPath, allowlist));
462
- const localServiceNames = new Set(localServices.map(s => s.name));
463
- const mergedServices = [
464
- ...localServices,
465
- ...publishedServices.filter(s => !localServiceNames.has(s.name))
466
- ];
467
- const services = filterServices(mergedServices, config.servicesToDeploy);
468
- for (let i = 0; i < services.length; i++) {
469
- const service = services[i];
470
- const isLast = i === services.length - 1;
471
- const prefix = isLast ? '└─' : '├─';
472
- const source = localServiceNames.has(service.name) ? 'local' : 'published';
473
- this.eventEmitter.emitLog('info', `${prefix} ${service.name} (${source})`, undefined, 'Discovery');
474
- }
475
- this.eventEmitter.emitPhaseComplete('discovery');
476
- this.eventEmitter.emitPhaseStart('dependency-analysis');
477
- const sorted = (0, exports.sortByDependencies)(services);
478
- for (let i = 0; i < sorted.length; i++) {
479
- const service = sorted[i];
480
- const isLast = i === sorted.length - 1;
481
- const prefix = isLast ? '└─' : '├─';
482
- const deps = service.dependencies.length > 0
483
- ? ` (requires: ${service.dependencies.join(', ')})`
484
- : '';
485
- this.eventEmitter.emitLog('info', `${prefix} ${service.name}${deps}`, undefined, 'DependencyGraph');
449
+ async prepareDeployment(config, liftEither, throwE) {
450
+ this.eventEmitter.emitPhaseStart('configuration');
451
+ const rootConfigPath = path.join(config.rootPath, 'pulumix.yaml');
452
+ const rawConfig = await liftEither(parseYamlFile(rootConfigPath));
453
+ const rootConfig = await liftEither((0, project_1.validateProjectConfig)(rawConfig, rootConfigPath));
454
+ const stacks = rootConfig.stacks ?? {};
455
+ const globalConfig = stacks[config.stackName] ?? {};
456
+ this.eventEmitter.emitPhaseComplete('configuration');
457
+ this.eventEmitter.emitPhaseStart('discovery');
458
+ const localServices = await liftEither(await (0, exports.discoverServices)(config.rootPath));
459
+ const allowlist = rootConfig.services?.allowed ?? [];
460
+ const publishedServices = await liftEither(await (0, exports.discoverPublishedServices)(config.rootPath, allowlist));
461
+ const localServiceNames = new Set(localServices.map(s => s.name));
462
+ const mergedServices = [
463
+ ...localServices,
464
+ ...publishedServices.filter(s => !localServiceNames.has(s.name))
465
+ ];
466
+ const services = filterServices(mergedServices, config.servicesToDeploy);
467
+ for (let i = 0; i < services.length; i++) {
468
+ const service = services[i];
469
+ const isLast = i === services.length - 1;
470
+ const prefix = isLast ? '└─' : '├─';
471
+ const source = localServiceNames.has(service.name) ? 'local' : 'published';
472
+ this.eventEmitter.emitLog('info', `${prefix} ${service.name} (${source})`, undefined, 'Discovery');
473
+ }
474
+ this.eventEmitter.emitPhaseComplete('discovery');
475
+ this.eventEmitter.emitPhaseStart('dependency-analysis');
476
+ const sorted = (0, exports.sortByDependencies)(services);
477
+ for (let i = 0; i < sorted.length; i++) {
478
+ const service = sorted[i];
479
+ const isLast = i === sorted.length - 1;
480
+ const prefix = isLast ? '└─' : '├─';
481
+ const deps = service.dependencies.length > 0
482
+ ? ` (requires: ${service.dependencies.join(', ')})`
483
+ : '';
484
+ this.eventEmitter.emitLog('info', `${prefix} ${service.name}${deps}`, undefined, 'DependencyGraph');
485
+ }
486
+ this.eventEmitter.emitPhaseComplete('dependency-analysis');
487
+ const hostRegistry = getConfigValue(globalConfig, 'hostRegistry').orDefault('localhost:5001');
488
+ const clusterRegistry = getConfigValue(globalConfig, 'clusterRegistry').orDefault(hostRegistry);
489
+ const platformConfig = getConfigValue(globalConfig, 'platform').extract();
490
+ const platforms = platformConfig
491
+ ? (Array.isArray(platformConfig) ? platformConfig : [platformConfig])
492
+ : undefined;
493
+ const hooks = (0, hooks_1.getHooksForStack)(rootConfig.hooks, config.stackName);
494
+ const servicesToBuild = sorted.filter(s => s.hasDockerfile);
495
+ const preBuildHooks = hooks.filter(h => h.stage === 'pre-build');
496
+ if (preBuildHooks.length > 0) {
497
+ this.eventEmitter.emitPhaseStart('bootstrap');
498
+ const hookResult = await (0, hooks_1.executeHooksForStage)('pre-build', hooks, config.rootPath, this.eventEmitter);
499
+ if (hookResult.isLeft()) {
500
+ this.eventEmitter.emitPhaseComplete('bootstrap', false);
501
+ throw throwE(hookResult.extract());
486
502
  }
487
- this.eventEmitter.emitPhaseComplete('dependency-analysis');
488
- const hostRegistry = getConfigValue(globalConfig, 'hostRegistry').orDefault('localhost:5001');
489
- const clusterRegistry = getConfigValue(globalConfig, 'clusterRegistry').orDefault(hostRegistry);
490
- const platformConfig = getConfigValue(globalConfig, 'platform').extract();
491
- const platforms = platformConfig
492
- ? (Array.isArray(platformConfig) ? platformConfig : [platformConfig])
493
- : undefined;
494
- const hooks = (0, hooks_1.getHooksForStack)(rootConfig.hooks, config.stackName);
495
- const servicesToBuild = sorted.filter(s => s.hasDockerfile);
496
- const preBuildHooks = hooks.filter(h => h.stage === 'pre-build');
497
- if (preBuildHooks.length > 0) {
498
- this.eventEmitter.emitPhaseStart('bootstrap');
499
- const hookResult = await (0, hooks_1.executeHooksForStage)('pre-build', hooks, config.rootPath, this.eventEmitter);
500
- if (hookResult.isLeft()) {
501
- this.eventEmitter.emitPhaseComplete('bootstrap', false);
502
- throw throwE(hookResult.extract());
503
- }
504
- this.eventEmitter.emitPhaseComplete('bootstrap');
503
+ this.eventEmitter.emitPhaseComplete('bootstrap');
504
+ }
505
+ const builtImages = {};
506
+ if (servicesToBuild.length > 0) {
507
+ this.eventEmitter.emitPhaseStart('image-build');
508
+ const preparedBuilds = await Promise.all(servicesToBuild.map(service => prepareBuild(service, hostRegistry, config.rootPath)));
509
+ const cachedBuilds = preparedBuilds.filter(b => b.cached);
510
+ const uncachedBuilds = preparedBuilds.filter(b => !b.cached);
511
+ for (const build of cachedBuilds) {
512
+ this.eventEmitter.emitTaskStart(build.service.name, build.service.name, 'image-build', build.contentHash);
513
+ this.eventEmitter.emitTaskUpdate(build.service.name, `unchanged (${build.contentHash})`);
514
+ const imageWithoutRegistry = (build.imageRef || build.imageTag).replace(`${hostRegistry}/`, '');
515
+ builtImages[build.service.name] = `${clusterRegistry}/${imageWithoutRegistry}`;
516
+ this.eventEmitter.emitTaskComplete(build.service.name, true, true, build.contentHash);
505
517
  }
506
- const builtImages = {};
507
- if (servicesToBuild.length > 0) {
508
- this.eventEmitter.emitPhaseStart('image-build');
509
- const preparedBuilds = await Promise.all(servicesToBuild.map(service => prepareBuild(service, hostRegistry, config.rootPath)));
510
- const cachedBuilds = preparedBuilds.filter(b => b.cached);
511
- const uncachedBuilds = preparedBuilds.filter(b => !b.cached);
512
- for (const build of cachedBuilds) {
518
+ if (uncachedBuilds.length > 0) {
519
+ for (const build of uncachedBuilds) {
513
520
  this.eventEmitter.emitTaskStart(build.service.name, build.service.name, 'image-build', build.contentHash);
514
- this.eventEmitter.emitTaskUpdate(build.service.name, `unchanged (${build.contentHash})`);
515
- const imageWithoutRegistry = (build.imageRef || build.imageTag).replace(`${hostRegistry}/`, '');
516
- builtImages[build.service.name] = `${clusterRegistry}/${imageWithoutRegistry}`;
517
- this.eventEmitter.emitTaskComplete(build.service.name, true, true, build.contentHash);
521
+ this.eventEmitter.emitTaskUpdate(build.service.name, 'building');
518
522
  }
519
- if (uncachedBuilds.length > 0) {
520
- for (const build of uncachedBuilds) {
521
- this.eventEmitter.emitTaskStart(build.service.name, build.service.name, 'image-build', build.contentHash);
522
- this.eventEmitter.emitTaskUpdate(build.service.name, 'building');
523
+ const bakeServices = uncachedBuilds.map(build => ({
524
+ name: build.service.name,
525
+ contextPath: build.contextPath,
526
+ dockerfile: build.dockerfile,
527
+ tag: build.imageTag,
528
+ contentHash: build.contentHash,
529
+ platforms,
530
+ }));
531
+ const bakeConfig = (0, docker_1.generateBakeConfig)(bakeServices);
532
+ const bakeFilePath = path.join(config.rootPath, 'dist', 'docker-bake.json');
533
+ if (!fs.existsSync(path.dirname(bakeFilePath))) {
534
+ fs.mkdirSync(path.dirname(bakeFilePath), { recursive: true });
535
+ }
536
+ fs.writeFileSync(bakeFilePath, JSON.stringify(bakeConfig, null, 2));
537
+ const lastStepShown = new Map();
538
+ const completedTargets = new Set();
539
+ const buildHashMap = new Map(uncachedBuilds.map(b => [b.service.name, b.contentHash]));
540
+ const bakeResult = await (0, docker_1.runBake)(bakeFilePath, bakeServices, (progress) => {
541
+ if (!progress.target || progress.target.startsWith('_'))
542
+ return;
543
+ const isExportStep = progress.message.includes('exporting');
544
+ if (progress.done && isExportStep) {
545
+ completedTargets.add(progress.target);
546
+ const contentHash = buildHashMap.get(progress.target);
547
+ this.eventEmitter.emitTaskComplete(progress.target, !progress.error, false, contentHash);
548
+ return;
523
549
  }
524
- const bakeServices = uncachedBuilds.map(build => ({
525
- name: build.service.name,
526
- contextPath: build.contextPath,
527
- dockerfile: build.dockerfile,
528
- tag: build.imageTag,
529
- contentHash: build.contentHash,
530
- platforms,
531
- }));
532
- const bakeConfig = (0, docker_1.generateBakeConfig)(bakeServices);
533
- const bakeFilePath = path.join(config.rootPath, 'dist', 'docker-bake.json');
534
- if (!fs.existsSync(path.dirname(bakeFilePath))) {
535
- fs.mkdirSync(path.dirname(bakeFilePath), { recursive: true });
550
+ let message = progress.message;
551
+ if (progress.step) {
552
+ message = `[${progress.step.current}/${progress.step.total}] ${progress.message}`;
536
553
  }
537
- fs.writeFileSync(bakeFilePath, JSON.stringify(bakeConfig, null, 2));
538
- const lastStepShown = new Map();
539
- const completedTargets = new Set();
540
- const buildHashMap = new Map(uncachedBuilds.map(b => [b.service.name, b.contentHash]));
541
- const bakeResult = await (0, docker_1.runBake)(bakeFilePath, bakeServices, (progress) => {
542
- if (!progress.target || progress.target.startsWith('_'))
543
- return;
544
- const isExportStep = progress.message.includes('exporting');
545
- if (progress.done && isExportStep) {
546
- completedTargets.add(progress.target);
547
- const contentHash = buildHashMap.get(progress.target);
548
- this.eventEmitter.emitTaskComplete(progress.target, !progress.error, false, contentHash);
549
- return;
550
- }
551
- let message = progress.message;
552
- if (progress.step) {
553
- message = `[${progress.step.current}/${progress.step.total}] ${progress.message}`;
554
- }
555
- const lastShown = lastStepShown.get(progress.target);
556
- if (lastShown === message)
557
- return;
558
- lastStepShown.set(progress.target, message);
559
- this.eventEmitter.emitTaskUpdate(progress.target, message);
560
- });
561
- if (bakeResult.isLeft()) {
562
- const error = bakeResult.extract();
563
- for (const build of uncachedBuilds) {
564
- if (!completedTargets.has(build.service.name)) {
565
- this.eventEmitter.emitTaskComplete(build.service.name, false, false, build.contentHash);
566
- }
567
- }
568
- this.eventEmitter.emitLog('error', error.message, undefined, 'Build');
569
- this.eventEmitter.emitPhaseComplete('image-build', false);
570
- throw error;
571
- }
572
- const imageRefs = bakeResult.unsafeCoerce();
554
+ const lastShown = lastStepShown.get(progress.target);
555
+ if (lastShown === message)
556
+ return;
557
+ lastStepShown.set(progress.target, message);
558
+ this.eventEmitter.emitTaskUpdate(progress.target, message);
559
+ });
560
+ if (bakeResult.isLeft()) {
561
+ const error = bakeResult.extract();
573
562
  for (const build of uncachedBuilds) {
574
- const imageRef = imageRefs.get(build.service.name) || build.imageTag;
575
- const imageWithoutRegistry = imageRef.replace(`${hostRegistry}/`, '');
576
- builtImages[build.service.name] = `${clusterRegistry}/${imageWithoutRegistry}`;
577
563
  if (!completedTargets.has(build.service.name)) {
578
- this.eventEmitter.emitTaskComplete(build.service.name, true, false, build.contentHash);
564
+ this.eventEmitter.emitTaskComplete(build.service.name, false, false, build.contentHash);
579
565
  }
580
566
  }
567
+ this.eventEmitter.emitLog('error', error.message, undefined, 'Build');
568
+ this.eventEmitter.emitPhaseComplete('image-build', false);
569
+ throw error;
570
+ }
571
+ const imageRefs = bakeResult.unsafeCoerce();
572
+ for (const build of uncachedBuilds) {
573
+ const imageRef = imageRefs.get(build.service.name) || build.imageTag;
574
+ const imageWithoutRegistry = imageRef.replace(`${hostRegistry}/`, '');
575
+ builtImages[build.service.name] = `${clusterRegistry}/${imageWithoutRegistry}`;
576
+ if (!completedTargets.has(build.service.name)) {
577
+ this.eventEmitter.emitTaskComplete(build.service.name, true, false, build.contentHash);
578
+ }
581
579
  }
582
- this.eventEmitter.emitPhaseComplete('image-build');
583
580
  }
584
- const postBuildHooks = hooks.filter(h => h.stage === 'post-build');
585
- if (postBuildHooks.length > 0) {
586
- const hookResult = await (0, hooks_1.executeHooksForStage)('post-build', hooks, config.rootPath, this.eventEmitter);
587
- if (hookResult.isLeft()) {
588
- throw throwE(hookResult.extract());
581
+ this.eventEmitter.emitPhaseComplete('image-build');
582
+ }
583
+ const postBuildHooks = hooks.filter(h => h.stage === 'post-build');
584
+ if (postBuildHooks.length > 0) {
585
+ const hookResult = await (0, hooks_1.executeHooksForStage)('post-build', hooks, config.rootPath, this.eventEmitter);
586
+ if (hookResult.isLeft()) {
587
+ throw throwE(hookResult.extract());
588
+ }
589
+ }
590
+ const backendUrl = resolveBackendUrl(config.rootPath, rootConfig, config.stackName);
591
+ const workDir = resolveWorkDir(config.rootPath, rootConfig, config.stackName);
592
+ if (!fs.existsSync(workDir)) {
593
+ fs.mkdirSync(workDir, { recursive: true });
594
+ }
595
+ const projectName = rootConfig.name ?? 'pulumix-project';
596
+ const outputs = {};
597
+ const program = async () => {
598
+ for (const service of sorted) {
599
+ const resolved = (0, exports.resolveStackConfig)(service, config.stackName);
600
+ const ctx = {
601
+ stackName: config.stackName,
602
+ serviceName: service.name,
603
+ metadata: service.metadata,
604
+ observability: service.observability,
605
+ security: service.security,
606
+ config: resolved.stackConfig,
607
+ globalConfig,
608
+ dependencies: outputs,
609
+ image: builtImages[service.name]
610
+ };
611
+ const deployFnResult = await loadDeployFunction(service);
612
+ if (deployFnResult.isLeft()) {
613
+ throw deployFnResult.extract();
614
+ }
615
+ const deployFn = deployFnResult.unsafeCoerce();
616
+ const result = await deployFn(ctx);
617
+ if (result?.outputs) {
618
+ outputs[service.name] = result.outputs;
589
619
  }
590
620
  }
621
+ return outputs;
622
+ };
623
+ return { sorted, builtImages, globalConfig, rootConfig, hooks, backendUrl, workDir, projectName, program, outputs };
624
+ }
625
+ deploy(config) {
626
+ const startTime = Date.now();
627
+ return (0, EitherAsync_1.EitherAsync)(async ({ liftEither, throwE }) => {
628
+ await liftEither(this.validateEnvironment(config.stackName));
629
+ const { sorted, hooks, backendUrl, workDir, projectName, program, outputs } = await this.prepareDeployment(config, liftEither, throwE);
591
630
  const preDeployHooks = hooks.filter(h => h.stage === 'pre-deploy');
592
631
  if (preDeployHooks.length > 0) {
593
632
  const hookResult = await (0, hooks_1.executeHooksForStage)('pre-deploy', hooks, config.rootPath, this.eventEmitter);
@@ -596,39 +635,6 @@ class Orchestrator {
596
635
  }
597
636
  }
598
637
  this.eventEmitter.emitPhaseStart('deployment');
599
- const outputs = {};
600
- const program = async () => {
601
- for (const service of sorted) {
602
- const resolved = (0, exports.resolveStackConfig)(service, config.stackName);
603
- const ctx = {
604
- stackName: config.stackName,
605
- serviceName: service.name,
606
- metadata: service.metadata,
607
- observability: service.observability,
608
- security: service.security,
609
- config: resolved.stackConfig,
610
- globalConfig,
611
- dependencies: outputs,
612
- image: builtImages[service.name]
613
- };
614
- const deployFnResult = await loadDeployFunction(service);
615
- if (deployFnResult.isLeft()) {
616
- throw deployFnResult.extract();
617
- }
618
- const deployFn = deployFnResult.unsafeCoerce();
619
- const result = await deployFn(ctx);
620
- if (result?.outputs) {
621
- outputs[service.name] = result.outputs;
622
- }
623
- }
624
- return outputs;
625
- };
626
- const backendUrl = resolveBackendUrl(config.rootPath, rootConfig, config.stackName);
627
- const workDir = resolveWorkDir(config.rootPath, rootConfig, config.stackName);
628
- if (!fs.existsSync(workDir)) {
629
- fs.mkdirSync(workDir, { recursive: true });
630
- }
631
- const projectName = rootConfig.name ?? 'pulumix-project';
632
638
  const stack = await automation_1.LocalWorkspace.createOrSelectStack({
633
639
  stackName: config.stackName,
634
640
  projectName,
@@ -673,6 +679,77 @@ class Orchestrator {
673
679
  };
674
680
  });
675
681
  }
682
+ preview(config) {
683
+ const startTime = Date.now();
684
+ return (0, EitherAsync_1.EitherAsync)(async ({ liftEither, throwE }) => {
685
+ await liftEither(this.validateEnvironment(config.stackName));
686
+ const { backendUrl, workDir, projectName, program } = await this.prepareDeployment(config, liftEither, throwE);
687
+ this.eventEmitter.emitPhaseStart('deployment');
688
+ const stack = await automation_1.LocalWorkspace.createOrSelectStack({ stackName: config.stackName, projectName, program }, { workDir, projectSettings: { name: projectName, runtime: 'nodejs', backend: { url: backendUrl } } });
689
+ try {
690
+ const previewResult = await stack.preview({
691
+ onOutput: config.onOutput || ((msg) => this.eventEmitter.emitLog('info', msg, undefined, 'Deploy')),
692
+ });
693
+ this.eventEmitter.emitPhaseComplete('deployment');
694
+ return {
695
+ success: true,
696
+ stack: config.stackName,
697
+ changeSummary: previewResult.changeSummary ?? {},
698
+ duration: Date.now() - startTime
699
+ };
700
+ }
701
+ catch (err) {
702
+ this.eventEmitter.emitPhaseComplete('deployment', false);
703
+ const message = err instanceof Error ? err.message : String(err);
704
+ throw new Error(`Pulumi preview failed: ${message}`);
705
+ }
706
+ });
707
+ }
708
+ status(config) {
709
+ return (0, EitherAsync_1.EitherAsync)(async ({ liftEither }) => {
710
+ await liftEither(this.validateEnvironment(config.stackName));
711
+ const rootConfigPath = path.join(config.rootPath, 'pulumix.yaml');
712
+ const rawConfig = await liftEither(parseYamlFile(rootConfigPath));
713
+ const rootConfig = await liftEither((0, project_1.validateProjectConfig)(rawConfig, rootConfigPath));
714
+ const backendUrl = resolveBackendUrl(config.rootPath, rootConfig, config.stackName);
715
+ const workDir = resolveWorkDir(config.rootPath, rootConfig, config.stackName);
716
+ if (!fs.existsSync(workDir)) {
717
+ fs.mkdirSync(workDir, { recursive: true });
718
+ }
719
+ const projectName = rootConfig.name ?? 'pulumix-project';
720
+ const stack = await automation_1.LocalWorkspace.createOrSelectStack({ stackName: config.stackName, projectName, program: async () => { } }, { workDir, projectSettings: { name: projectName, runtime: 'nodejs', backend: { url: backendUrl } } });
721
+ const stackOutputs = await stack.outputs();
722
+ const outputs = Object.fromEntries(Object.entries(stackOutputs).map(([k, v]) => [k, v.value]));
723
+ return { success: true, stack: config.stackName, servicesDeployed: 0, duration: 0, outputs };
724
+ });
725
+ }
726
+ refresh(config) {
727
+ const startTime = Date.now();
728
+ return (0, EitherAsync_1.EitherAsync)(async ({ liftEither }) => {
729
+ await liftEither(this.validateEnvironment(config.stackName));
730
+ const rootConfigPath = path.join(config.rootPath, 'pulumix.yaml');
731
+ const rawConfig = await liftEither(parseYamlFile(rootConfigPath));
732
+ const rootConfig = await liftEither((0, project_1.validateProjectConfig)(rawConfig, rootConfigPath));
733
+ const backendUrl = resolveBackendUrl(config.rootPath, rootConfig, config.stackName);
734
+ const workDir = resolveWorkDir(config.rootPath, rootConfig, config.stackName);
735
+ if (!fs.existsSync(workDir)) {
736
+ fs.mkdirSync(workDir, { recursive: true });
737
+ }
738
+ const projectName = rootConfig.name ?? 'pulumix-project';
739
+ const program = async () => { };
740
+ const stack = await automation_1.LocalWorkspace.createOrSelectStack({ stackName: config.stackName, projectName, program }, { workDir, projectSettings: { name: projectName, runtime: 'nodejs', backend: { url: backendUrl } } });
741
+ await stack.refresh({
742
+ onOutput: config.onOutput || ((msg) => this.eventEmitter.emitLog('info', msg, undefined, 'Deploy')),
743
+ });
744
+ return {
745
+ success: true,
746
+ stack: config.stackName,
747
+ servicesDeployed: 0,
748
+ duration: Date.now() - startTime,
749
+ outputs: {}
750
+ };
751
+ });
752
+ }
676
753
  destroy(config) {
677
754
  const startTime = Date.now();
678
755
  return (0, EitherAsync_1.EitherAsync)(async ({ liftEither }) => {
@@ -682,6 +759,9 @@ class Orchestrator {
682
759
  const rootConfig = await liftEither((0, project_1.validateProjectConfig)(rawConfig, rootConfigPath));
683
760
  const backendUrl = resolveBackendUrl(config.rootPath, rootConfig, config.stackName);
684
761
  const workDir = resolveWorkDir(config.rootPath, rootConfig, config.stackName);
762
+ if (!fs.existsSync(workDir)) {
763
+ fs.mkdirSync(workDir, { recursive: true });
764
+ }
685
765
  const projectName = rootConfig.name ?? 'pulumix-project';
686
766
  const program = async () => {
687
767
  };