@pulumix/core 0.8.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 (47) 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/events.d.ts +1 -1
  37. package/dist/orchestrator/events.d.ts.map +1 -1
  38. package/dist/orchestrator/events.js +3 -2
  39. package/dist/orchestrator/events.js.map +1 -1
  40. package/dist/orchestrator/index.d.ts +10 -0
  41. package/dist/orchestrator/index.d.ts.map +1 -1
  42. package/dist/orchestrator/index.js +247 -165
  43. package/dist/orchestrator/index.js.map +1 -1
  44. package/dist/types/events.d.ts +1 -0
  45. package/dist/types/events.d.ts.map +1 -1
  46. package/dist/types/events.js.map +1 -1
  47. package/package.json +3 -2
@@ -16,6 +16,12 @@ export interface OrchestratorResult {
16
16
  readonly duration: number;
17
17
  readonly outputs: Record<string, Record<string, unknown>>;
18
18
  }
19
+ export interface PreviewResult {
20
+ readonly success: boolean;
21
+ readonly stack: string;
22
+ readonly changeSummary: Record<string, number>;
23
+ readonly duration: number;
24
+ }
19
25
  export declare const discoverServices: (rootPath: string) => Promise<Either<DeployError, readonly DiscoveredService[]>>;
20
26
  export declare const discoverPublishedServices: (rootPath: string, allowlist: string[]) => Promise<Either<DeployError, readonly DiscoveredService[]>>;
21
27
  export declare const sortByDependencies: (services: readonly DiscoveredService[]) => readonly DiscoveredService[];
@@ -25,7 +31,11 @@ export declare class Orchestrator {
25
31
  constructor(eventEmitter?: OrchestratorEventEmitter);
26
32
  getEventEmitter(): OrchestratorEventEmitter;
27
33
  private validateEnvironment;
34
+ private prepareDeployment;
28
35
  deploy(config: OrchestratorConfig): EitherAsync<DeployError, OrchestratorResult>;
36
+ preview(config: OrchestratorConfig): EitherAsync<DeployError, PreviewResult>;
37
+ status(config: OrchestratorConfig): EitherAsync<DeployError, OrchestratorResult>;
38
+ refresh(config: OrchestratorConfig): EitherAsync<DeployError, OrchestratorResult>;
29
39
  destroy(config: OrchestratorConfig): EitherAsync<DeployError, OrchestratorResult>;
30
40
  }
31
41
  export declare const createOrchestrator: (eventEmitter?: OrchestratorEventEmitter) => Orchestrator;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/orchestrator/index.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,MAAM,EAAe,MAAM,kBAAkB,CAAA;AAItD,OAAO,EAGL,WAAW,EACZ,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EAAE,wBAAwB,EAAsB,MAAM,UAAU,CAAA;AACvE,OAAO,EACL,iBAAiB,EACjB,eAAe,EAGhB,MAAM,kBAAkB,CAAA;AAgCzB,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAE7C,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAC9C;AAKD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CAC1D;AAmND,eAAO,MAAM,gBAAgB,GAAU,UAAU,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,SAAS,iBAAiB,EAAE,CAAC,CAqGlH,CAAA;AAqBD,eAAO,MAAM,yBAAyB,GACpC,UAAU,MAAM,EAChB,WAAW,MAAM,EAAE,KAClB,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,SAAS,iBAAiB,EAAE,CAAC,CAwH3D,CAAA;AAKD,eAAO,MAAM,kBAAkB,GAC7B,UAAU,SAAS,iBAAiB,EAAE,KACrC,SAAS,iBAAiB,EAwB5B,CAAA;AAKD,eAAO,MAAM,kBAAkB,GAC7B,SAAS,iBAAiB,EAC1B,WAAW,MAAM,KAChB,eAUF,CAAA;AAmKD,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA0B;gBAE3C,YAAY,CAAC,EAAE,wBAAwB;IAInD,eAAe,IAAI,wBAAwB;IAO3C,OAAO,CAAC,mBAAmB;IAyB3B,MAAM,CAAC,MAAM,EAAE,kBAAkB,GAAG,WAAW,CAAC,WAAW,EAAE,kBAAkB,CAAC;IA6VhF,OAAO,CAAC,MAAM,EAAE,kBAAkB,GAAG,WAAW,CAAC,WAAW,EAAE,kBAAkB,CAAC;CAqDlF;AAKD,eAAO,MAAM,kBAAkB,GAAI,eAAe,wBAAwB,KAAG,YAC7C,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/orchestrator/index.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,MAAM,EAAe,MAAM,kBAAkB,CAAA;AAItD,OAAO,EAGL,WAAW,EACZ,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EAAE,wBAAwB,EAAsB,MAAM,UAAU,CAAA;AACvE,OAAO,EACL,iBAAiB,EACjB,eAAe,EAGhB,MAAM,kBAAkB,CAAA;AAgCzB,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAE7C,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAC9C;AAKD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CAC1D;AAKD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAoND,eAAO,MAAM,gBAAgB,GAAU,UAAU,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,SAAS,iBAAiB,EAAE,CAAC,CAqGlH,CAAA;AAqBD,eAAO,MAAM,yBAAyB,GACpC,UAAU,MAAM,EAChB,WAAW,MAAM,EAAE,KAClB,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,SAAS,iBAAiB,EAAE,CAAC,CAwH3D,CAAA;AAKD,eAAO,MAAM,kBAAkB,GAC7B,UAAU,SAAS,iBAAiB,EAAE,KACrC,SAAS,iBAAiB,EAwB5B,CAAA;AAKD,eAAO,MAAM,kBAAkB,GAC7B,SAAS,iBAAiB,EAC1B,WAAW,MAAM,KAChB,eAUF,CAAA;AAmKD,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA0B;gBAE3C,YAAY,CAAC,EAAE,wBAAwB;IAInD,eAAe,IAAI,wBAAwB;IAO3C,OAAO,CAAC,mBAAmB;YAyBb,iBAAiB;IAoQ/B,MAAM,CAAC,MAAM,EAAE,kBAAkB,GAAG,WAAW,CAAC,WAAW,EAAE,kBAAkB,CAAC;IAqFhF,OAAO,CAAC,MAAM,EAAE,kBAAkB,GAAG,WAAW,CAAC,WAAW,EAAE,aAAa,CAAC;IAyC5E,MAAM,CAAC,MAAM,EAAE,kBAAkB,GAAG,WAAW,CAAC,WAAW,EAAE,kBAAkB,CAAC;IAkChF,OAAO,CAAC,MAAM,EAAE,kBAAkB,GAAG,WAAW,CAAC,WAAW,EAAE,kBAAkB,CAAC;IA2CjF,OAAO,CAAC,MAAM,EAAE,kBAAkB,GAAG,WAAW,CAAC,WAAW,EAAE,kBAAkB,CAAC;CAyDlF;AAKD,eAAO,MAAM,kBAAkB,GAAI,eAAe,wBAAwB,KAAG,YAC7C,CAAA"}
@@ -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,148 +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');
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());
474
502
  }
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');
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);
486
517
  }
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());
518
+ if (uncachedBuilds.length > 0) {
519
+ for (const build of uncachedBuilds) {
520
+ this.eventEmitter.emitTaskStart(build.service.name, build.service.name, 'image-build', build.contentHash);
521
+ this.eventEmitter.emitTaskUpdate(build.service.name, 'building');
503
522
  }
504
- this.eventEmitter.emitPhaseComplete('bootstrap');
505
- }
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) {
513
- this.eventEmitter.emitTaskStart(build.service.name);
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);
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 });
518
535
  }
519
- if (uncachedBuilds.length > 0) {
520
- for (const build of uncachedBuilds) {
521
- this.eventEmitter.emitTaskStart(build.service.name);
522
- this.eventEmitter.emitTaskUpdate(build.service.name, 'building');
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 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
- this.eventEmitter.emitTaskComplete(progress.target, !progress.error);
547
- return;
548
- }
549
- let message = progress.message;
550
- if (progress.step) {
551
- message = `[${progress.step.current}/${progress.step.total}] ${progress.message}`;
552
- }
553
- const lastShown = lastStepShown.get(progress.target);
554
- if (lastShown === message)
555
- return;
556
- lastStepShown.set(progress.target, message);
557
- this.eventEmitter.emitTaskUpdate(progress.target, message);
558
- });
559
- if (bakeResult.isLeft()) {
560
- const error = bakeResult.extract();
561
- for (const build of uncachedBuilds) {
562
- if (!completedTargets.has(build.service.name)) {
563
- this.eventEmitter.emitTaskComplete(build.service.name, false);
564
- }
565
- }
566
- this.eventEmitter.emitLog('error', error.message, undefined, 'Build');
567
- this.eventEmitter.emitPhaseComplete('image-build', false);
568
- throw error;
569
- }
570
- 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();
571
562
  for (const build of uncachedBuilds) {
572
- const imageRef = imageRefs.get(build.service.name) || build.imageTag;
573
- const imageWithoutRegistry = imageRef.replace(`${hostRegistry}/`, '');
574
- builtImages[build.service.name] = `${clusterRegistry}/${imageWithoutRegistry}`;
575
563
  if (!completedTargets.has(build.service.name)) {
576
- this.eventEmitter.emitTaskComplete(build.service.name, true, false, build.contentHash);
564
+ this.eventEmitter.emitTaskComplete(build.service.name, false, false, build.contentHash);
577
565
  }
578
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
+ }
579
579
  }
580
- this.eventEmitter.emitPhaseComplete('image-build');
581
580
  }
582
- const postBuildHooks = hooks.filter(h => h.stage === 'post-build');
583
- if (postBuildHooks.length > 0) {
584
- const hookResult = await (0, hooks_1.executeHooksForStage)('post-build', hooks, config.rootPath, this.eventEmitter);
585
- if (hookResult.isLeft()) {
586
- 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;
587
619
  }
588
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);
589
630
  const preDeployHooks = hooks.filter(h => h.stage === 'pre-deploy');
590
631
  if (preDeployHooks.length > 0) {
591
632
  const hookResult = await (0, hooks_1.executeHooksForStage)('pre-deploy', hooks, config.rootPath, this.eventEmitter);
@@ -594,39 +635,6 @@ class Orchestrator {
594
635
  }
595
636
  }
596
637
  this.eventEmitter.emitPhaseStart('deployment');
597
- const outputs = {};
598
- const program = async () => {
599
- for (const service of sorted) {
600
- const resolved = (0, exports.resolveStackConfig)(service, config.stackName);
601
- const ctx = {
602
- stackName: config.stackName,
603
- serviceName: service.name,
604
- metadata: service.metadata,
605
- observability: service.observability,
606
- security: service.security,
607
- config: resolved.stackConfig,
608
- globalConfig,
609
- dependencies: outputs,
610
- image: builtImages[service.name]
611
- };
612
- const deployFnResult = await loadDeployFunction(service);
613
- if (deployFnResult.isLeft()) {
614
- throw deployFnResult.extract();
615
- }
616
- const deployFn = deployFnResult.unsafeCoerce();
617
- const result = await deployFn(ctx);
618
- if (result?.outputs) {
619
- outputs[service.name] = result.outputs;
620
- }
621
- }
622
- return outputs;
623
- };
624
- const backendUrl = resolveBackendUrl(config.rootPath, rootConfig, config.stackName);
625
- const workDir = resolveWorkDir(config.rootPath, rootConfig, config.stackName);
626
- if (!fs.existsSync(workDir)) {
627
- fs.mkdirSync(workDir, { recursive: true });
628
- }
629
- const projectName = rootConfig.name ?? 'pulumix-project';
630
638
  const stack = await automation_1.LocalWorkspace.createOrSelectStack({
631
639
  stackName: config.stackName,
632
640
  projectName,
@@ -671,6 +679,77 @@ class Orchestrator {
671
679
  };
672
680
  });
673
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
+ }
674
753
  destroy(config) {
675
754
  const startTime = Date.now();
676
755
  return (0, EitherAsync_1.EitherAsync)(async ({ liftEither }) => {
@@ -680,6 +759,9 @@ class Orchestrator {
680
759
  const rootConfig = await liftEither((0, project_1.validateProjectConfig)(rawConfig, rootConfigPath));
681
760
  const backendUrl = resolveBackendUrl(config.rootPath, rootConfig, config.stackName);
682
761
  const workDir = resolveWorkDir(config.rootPath, rootConfig, config.stackName);
762
+ if (!fs.existsSync(workDir)) {
763
+ fs.mkdirSync(workDir, { recursive: true });
764
+ }
683
765
  const projectName = rootConfig.name ?? 'pulumix-project';
684
766
  const program = async () => {
685
767
  };