freestyle-sandboxes 0.1.38 → 0.1.40

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/index.cjs CHANGED
@@ -6009,6 +6009,64 @@ function composeVmSpecs(specs) {
6009
6009
  }
6010
6010
 
6011
6011
  const DEFAULT_CONFIGURE_BASE_IMAGE = "FROM debian:trixie-slim";
6012
+ const RUN_COMMANDS_SYSTEMD_SERVICE_PREFIX = "freestyle-run-command";
6013
+ const WAIT_FOR_SYSTEMD_SERVICE_PREFIX = "freestyle-wait-for";
6014
+ function escapeRegExp(value) {
6015
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6016
+ }
6017
+ function getGeneratedServiceState(services, prefix) {
6018
+ const generatedPrefixes = [
6019
+ RUN_COMMANDS_SYSTEMD_SERVICE_PREFIX,
6020
+ WAIT_FOR_SYSTEMD_SERVICE_PREFIX
6021
+ ];
6022
+ let maxIndex = 0;
6023
+ let lastGeneratedServiceName;
6024
+ for (const service of services) {
6025
+ if (!service.name) {
6026
+ continue;
6027
+ }
6028
+ for (const generatedPrefix of generatedPrefixes) {
6029
+ const match = service.name.match(
6030
+ new RegExp(`^${escapeRegExp(generatedPrefix)}-(\\d+)$`)
6031
+ );
6032
+ if (!match) {
6033
+ continue;
6034
+ }
6035
+ if (generatedPrefix === prefix) {
6036
+ const index = Number.parseInt(match[1] ?? "0", 10);
6037
+ if (index > maxIndex) {
6038
+ maxIndex = index;
6039
+ }
6040
+ }
6041
+ lastGeneratedServiceName = service.name;
6042
+ break;
6043
+ }
6044
+ }
6045
+ return { maxIndex, lastGeneratedServiceName };
6046
+ }
6047
+ function appendServiceDependency(dependencies, dependency) {
6048
+ if (!dependency) {
6049
+ return dependencies ?? void 0;
6050
+ }
6051
+ const values = [...dependencies ?? []];
6052
+ if (!values.includes(dependency)) {
6053
+ values.push(dependency);
6054
+ }
6055
+ return values;
6056
+ }
6057
+ function buildWaitForScript(command, intervalSeconds) {
6058
+ const body = command.trim().split("\n").map((line) => ` ${line}`).join("\n");
6059
+ return [
6060
+ "while true; do",
6061
+ " if (",
6062
+ body,
6063
+ " ); then",
6064
+ " exit 0",
6065
+ " fi",
6066
+ ` sleep ${intervalSeconds}`,
6067
+ "done"
6068
+ ].join("\n");
6069
+ }
6012
6070
  function processSystemdServices(services, existingFiles = {}) {
6013
6071
  const additionalFiles = { ...existingFiles };
6014
6072
  const processedServices = [];
@@ -6043,6 +6101,63 @@ ${bash}`;
6043
6101
  }
6044
6102
  return { services: processedServices, additionalFiles };
6045
6103
  }
6104
+ function normalizeSystemdServices(services) {
6105
+ return services.map((service) => ({
6106
+ ...service,
6107
+ after: service.after?.map((s) => s.includes(".") ? s : `${s}.service`),
6108
+ requires: service.requires?.map(
6109
+ (s) => s.includes(".") ? s : `${s}.service`
6110
+ ),
6111
+ wantedBy: service.wantedBy?.map(
6112
+ (s) => s.includes(".") ? s : `${s}.service`
6113
+ )
6114
+ }));
6115
+ }
6116
+ function normalizePatchedServices(services) {
6117
+ return services.map((service) => ({
6118
+ ...service,
6119
+ after: service.after?.map((s) => s.includes(".") ? s : `${s}.service`),
6120
+ requires: service.requires?.map(
6121
+ (s) => s.includes(".") ? s : `${s}.service`
6122
+ ),
6123
+ wantedBy: service.wantedBy?.map(
6124
+ (s) => s.includes(".") ? s : `${s}.service`
6125
+ )
6126
+ }));
6127
+ }
6128
+ function normalizeTemplateForRequest(template) {
6129
+ const normalizedTemplate = {
6130
+ ...template ?? {}
6131
+ };
6132
+ if (normalizedTemplate.template) {
6133
+ normalizedTemplate.template = normalizeTemplateForRequest(
6134
+ normalizedTemplate.template
6135
+ );
6136
+ }
6137
+ if (normalizedTemplate.baseImage) {
6138
+ normalizedTemplate.baseImage = normalizeBaseImage(
6139
+ normalizedTemplate.baseImage
6140
+ )?.toRaw();
6141
+ }
6142
+ normalizedTemplate.git = normalizeGitOptions(normalizedTemplate.git);
6143
+ if (normalizedTemplate.systemd?.services) {
6144
+ const normalizedServices = normalizeSystemdServices(
6145
+ normalizedTemplate.systemd.services
6146
+ );
6147
+ const { services: processedServices, additionalFiles: bashFiles } = processSystemdServices(
6148
+ normalizedServices,
6149
+ normalizedTemplate.additionalFiles ?? {}
6150
+ );
6151
+ normalizedTemplate.systemd.services = processedServices;
6152
+ normalizedTemplate.additionalFiles = bashFiles;
6153
+ }
6154
+ if (normalizedTemplate.systemd?.patchedServices) {
6155
+ normalizedTemplate.systemd.patchedServices = normalizePatchedServices(
6156
+ normalizedTemplate.systemd.patchedServices
6157
+ );
6158
+ }
6159
+ return normalizedTemplate;
6160
+ }
6046
6161
  function normalizeGitOptions(git) {
6047
6162
  if (!git) return git;
6048
6163
  return {
@@ -6238,10 +6353,12 @@ class Vm {
6238
6353
  params: { vm_id: this.vmId },
6239
6354
  body: options
6240
6355
  });
6241
- const vmId = response.id;
6242
6356
  return {
6243
- vmId,
6244
- vm: new Vm({ vmId, freestyle: this._freestyle })
6357
+ forks: response.forks.map((fork) => ({
6358
+ vmId: fork.id,
6359
+ vm: this._freestyle.vms.ref({ vmId: fork.id })
6360
+ // Create a new Vm instance for the forked VM
6361
+ }))
6245
6362
  };
6246
6363
  }
6247
6364
  /**
@@ -6390,10 +6507,14 @@ class VmSpec {
6390
6507
  return { ...this.withDiscriminators };
6391
6508
  }
6392
6509
  mergeRaw(options) {
6510
+ const existingSnapshot = this.raw.snapshot;
6393
6511
  this.raw = composeCreateVmOptions([
6394
6512
  this.raw,
6395
6513
  options
6396
6514
  ]);
6515
+ if (existingSnapshot !== void 0 && this.raw.snapshot === void 0) {
6516
+ this.raw.snapshot = existingSnapshot;
6517
+ }
6397
6518
  return this;
6398
6519
  }
6399
6520
  clearBuilders() {
@@ -6460,6 +6581,14 @@ class VmSpec {
6460
6581
  const existing = this.raw.aptDeps ?? [];
6461
6582
  return this.mergeRaw({ aptDeps: [...existing, ...deps] });
6462
6583
  }
6584
+ users(users) {
6585
+ const existing = this.raw.users ?? [];
6586
+ return this.mergeRaw({ users: [...existing, ...users] });
6587
+ }
6588
+ groups(groups) {
6589
+ const existing = this.raw.groups ?? [];
6590
+ return this.mergeRaw({ groups: [...existing, ...groups] });
6591
+ }
6463
6592
  additionalFiles(files) {
6464
6593
  const existing = this.raw.additionalFiles ?? {};
6465
6594
  this.raw.additionalFiles = {
@@ -6469,9 +6598,74 @@ class VmSpec {
6469
6598
  return this;
6470
6599
  }
6471
6600
  runCommands(...commands) {
6472
- const image = normalizeBaseImage(this.raw.baseImage) ?? new VmBaseImage();
6473
- image.runCommands(...commands);
6474
- this.raw.baseImage = image;
6601
+ const normalizedCommands = commands.map((command) => command.trim()).filter((command) => command.length > 0);
6602
+ if (normalizedCommands.length === 0) {
6603
+ return this;
6604
+ }
6605
+ const existingSystemd = this.raw.systemd ?? {};
6606
+ const existingServices = [...existingSystemd.services ?? []];
6607
+ let { maxIndex: maxRunCommandIndex, lastGeneratedServiceName } = getGeneratedServiceState(
6608
+ existingServices,
6609
+ RUN_COMMANDS_SYSTEMD_SERVICE_PREFIX
6610
+ );
6611
+ for (const command of normalizedCommands) {
6612
+ const nextIndex = ++maxRunCommandIndex;
6613
+ const serviceName = `${RUN_COMMANDS_SYSTEMD_SERVICE_PREFIX}-${nextIndex}`;
6614
+ existingServices.push({
6615
+ name: serviceName,
6616
+ mode: "oneshot",
6617
+ deleteAfterSuccess: true,
6618
+ bash: command,
6619
+ ...lastGeneratedServiceName ? {
6620
+ after: [lastGeneratedServiceName],
6621
+ requires: [lastGeneratedServiceName]
6622
+ } : {}
6623
+ });
6624
+ lastGeneratedServiceName = serviceName;
6625
+ }
6626
+ this.raw.systemd = {
6627
+ ...existingSystemd,
6628
+ services: existingServices
6629
+ };
6630
+ return this;
6631
+ }
6632
+ waitFor(command, config = {}) {
6633
+ const trimmedCommand = command.trim();
6634
+ if (!trimmedCommand) {
6635
+ throw new Error("VmSpec.waitFor requires a non-empty command");
6636
+ }
6637
+ const intervalSeconds = config.intervalSeconds ?? 2;
6638
+ if (!Number.isFinite(intervalSeconds) || intervalSeconds <= 0) {
6639
+ throw new Error("VmSpec.waitFor intervalSeconds must be greater than 0");
6640
+ }
6641
+ const existingSystemd = this.raw.systemd ?? {};
6642
+ const existingServices = [...existingSystemd.services ?? []];
6643
+ const { maxIndex, lastGeneratedServiceName } = getGeneratedServiceState(
6644
+ existingServices,
6645
+ WAIT_FOR_SYSTEMD_SERVICE_PREFIX
6646
+ );
6647
+ const {
6648
+ intervalSeconds: _intervalSeconds,
6649
+ name,
6650
+ after,
6651
+ requires,
6652
+ timeoutSec,
6653
+ ...rest
6654
+ } = config;
6655
+ existingServices.push({
6656
+ ...rest,
6657
+ name: name ?? `${WAIT_FOR_SYSTEMD_SERVICE_PREFIX}-${maxIndex + 1}`,
6658
+ mode: "oneshot",
6659
+ // deleteAfterSuccess: true,
6660
+ timeoutSec: timeoutSec ?? 0,
6661
+ after: appendServiceDependency(after, lastGeneratedServiceName),
6662
+ requires: appendServiceDependency(requires, lastGeneratedServiceName),
6663
+ bash: buildWaitForScript(trimmedCommand, intervalSeconds)
6664
+ });
6665
+ this.raw.systemd = {
6666
+ ...existingSystemd,
6667
+ services: existingServices
6668
+ };
6475
6669
  return this;
6476
6670
  }
6477
6671
  repo(repo, path) {
@@ -6498,7 +6692,7 @@ class VmSpec {
6498
6692
  function isVmSpecLike$1(value) {
6499
6693
  return !!value && typeof value === "object" && "raw" in value && "with" in value;
6500
6694
  }
6501
- function isVmTemplateLike(value) {
6695
+ function isVmTemplateLike$1(value) {
6502
6696
  return !!value && typeof value === "object" && "raw" in value && "with" in value;
6503
6697
  }
6504
6698
  async function convertSpecSnapshotsToTemplates(spec, processBuilders = true) {
@@ -6624,21 +6818,8 @@ class VmsNamespace {
6624
6818
  }
6625
6819
  }
6626
6820
  if (options.systemd?.services) {
6627
- const normalizedServices = options.systemd.services.map(
6628
- (service) => {
6629
- return {
6630
- ...service,
6631
- after: service.after?.map(
6632
- (s) => s.includes(".") ? s : `${s}.service`
6633
- ),
6634
- requires: service.requires?.map(
6635
- (s) => s.includes(".") ? s : `${s}.service`
6636
- ),
6637
- wantedBy: service.wantedBy?.map(
6638
- (s) => s.includes(".") ? s : `${s}.service`
6639
- )
6640
- };
6641
- }
6821
+ const normalizedServices = normalizeSystemdServices(
6822
+ options.systemd.services
6642
6823
  );
6643
6824
  const { services: processedServices, additionalFiles: bashFiles } = processSystemdServices(
6644
6825
  normalizedServices,
@@ -6648,19 +6829,8 @@ class VmsNamespace {
6648
6829
  options.additionalFiles = bashFiles;
6649
6830
  }
6650
6831
  if (options.systemd?.patchedServices) {
6651
- options.systemd.patchedServices = options.systemd.patchedServices.map(
6652
- (service) => {
6653
- service.after = service.after?.map(
6654
- (s) => s.includes(".") ? s : `${s}.service`
6655
- );
6656
- service.requires = service.requires?.map(
6657
- (s) => s.includes(".") ? s : `${s}.service`
6658
- );
6659
- service.wantedBy = service.wantedBy?.map(
6660
- (s) => s.includes(".") ? s : `${s}.service`
6661
- );
6662
- return service;
6663
- }
6832
+ options.systemd.patchedServices = normalizePatchedServices(
6833
+ options.systemd.patchedServices
6664
6834
  );
6665
6835
  }
6666
6836
  if ("snapshot" in options) {
@@ -6693,7 +6863,7 @@ class VmsNamespace {
6693
6863
  const specBuilders = isVmSpecLike$1(
6694
6864
  options.spec
6695
6865
  ) ? collectSpecBuilders(options.spec) : void 0;
6696
- const templateBuilders = isVmTemplateLike(options.template) ? options.template.with : void 0;
6866
+ const templateBuilders = isVmTemplateLike$1(options.template) ? options.template.with : void 0;
6697
6867
  const builders = {
6698
6868
  ...templateBuilders || {},
6699
6869
  ...specBuilders || {},
@@ -6701,10 +6871,10 @@ class VmsNamespace {
6701
6871
  };
6702
6872
  const { with: _, spec: _spec, ...baseConfig } = options;
6703
6873
  let config = baseConfig;
6704
- if (isVmTemplateLike(config.template)) {
6874
+ if (isVmTemplateLike$1(config.template)) {
6705
6875
  config.template = await processTemplateTree(config.template);
6706
6876
  }
6707
- if (isVmTemplateLike(config.template)) {
6877
+ if (isVmTemplateLike$1(config.template)) {
6708
6878
  config.template = await ensureNestedTemplates(
6709
6879
  config.template,
6710
6880
  this.snapshots
@@ -6718,18 +6888,9 @@ class VmsNamespace {
6718
6888
  }
6719
6889
  }
6720
6890
  if (config.systemd?.services) {
6721
- const normalizedServices = config.systemd.services.map((service) => ({
6722
- ...service,
6723
- after: service.after?.map(
6724
- (s) => s.includes(".") ? s : `${s}.service`
6725
- ),
6726
- requires: service.requires?.map(
6727
- (s) => s.includes(".") ? s : `${s}.service`
6728
- ),
6729
- wantedBy: service.wantedBy?.map(
6730
- (s) => s.includes(".") ? s : `${s}.service`
6731
- )
6732
- }));
6891
+ const normalizedServices = normalizeSystemdServices(
6892
+ config.systemd.services
6893
+ );
6733
6894
  const { services: processedServices, additionalFiles: bashFiles } = processSystemdServices(
6734
6895
  normalizedServices,
6735
6896
  config.additionalFiles ?? {}
@@ -6738,28 +6899,20 @@ class VmsNamespace {
6738
6899
  config.additionalFiles = bashFiles;
6739
6900
  }
6740
6901
  if (config.systemd?.patchedServices) {
6741
- config.systemd.patchedServices = config.systemd.patchedServices.map(
6742
- (service) => {
6743
- service.after = service.after?.map(
6744
- (s) => s.includes(".") ? s : `${s}.service`
6745
- );
6746
- service.requires = service.requires?.map(
6747
- (s) => s.includes(".") ? s : `${s}.service`
6748
- );
6749
- service.wantedBy = service.wantedBy?.map(
6750
- (s) => s.includes(".") ? s : `${s}.service`
6751
- );
6752
- return service;
6753
- }
6902
+ config.systemd.patchedServices = normalizePatchedServices(
6903
+ config.systemd.patchedServices
6754
6904
  );
6755
6905
  }
6756
6906
  config.git = normalizeGitOptions(config.git);
6757
6907
  const serializedBaseImage = normalizeBaseImage(config.baseImage)?.toRaw();
6758
- const rawTemplate = isVmTemplateLike(config.template) ? config.template.raw : config.template;
6908
+ const rawTemplate = isVmTemplateLike$1(config.template) ? config.template.raw : config.template;
6759
6909
  const requestTemplate = serializedBaseImage ? {
6760
6910
  ...rawTemplate ?? {},
6761
6911
  baseImage: serializedBaseImage
6762
6912
  } : rawTemplate;
6913
+ const normalizedRequestTemplate = requestTemplate ? normalizeTemplateForRequest(
6914
+ requestTemplate
6915
+ ) : requestTemplate;
6763
6916
  const {
6764
6917
  baseImage: _baseImage,
6765
6918
  template: _template,
@@ -6768,7 +6921,7 @@ class VmsNamespace {
6768
6921
  const response = await this.freestyle._apiClient.post("/v1/vms", {
6769
6922
  body: {
6770
6923
  ...requestConfig,
6771
- template: requestTemplate,
6924
+ template: normalizedRequestTemplate,
6772
6925
  // Cast systemd since we've processed SystemdServiceInput[] to RawSystemdService[]
6773
6926
  systemd: config.systemd,
6774
6927
  // Normalize git options - default config to {}
@@ -6863,6 +7016,35 @@ function enhanceError(e) {
6863
7016
  if (!e.message.includes("create --snapshot")) {
6864
7017
  e.message = `${e.message}. Hint: use \`npx freestyle-sandboxes@latest vm create --snapshot ${e.body.snapshotId} --ssh --delete\` to debug.`;
6865
7018
  }
7019
+ if (e.body.diagnostics) {
7020
+ const d = e.body.diagnostics;
7021
+ const parts = [];
7022
+ if (d.serviceStates.length > 0) {
7023
+ parts.push("Service states:");
7024
+ for (const g of d.serviceStates) {
7025
+ parts.push(
7026
+ ` ${g.activeState}(${g.subState}): ${g.services.join(", ")}`
7027
+ );
7028
+ }
7029
+ }
7030
+ const allLogs = [
7031
+ ...d.failedServiceLogs ?? [],
7032
+ ...d.additionalFailedServiceLogs ?? []
7033
+ ];
7034
+ if (allLogs.length > 0) {
7035
+ parts.push("Failed service logs:");
7036
+ for (const l of allLogs) {
7037
+ parts.push(` --- ${l.unitName} ---`);
7038
+ parts.push(` ${l.log}`);
7039
+ }
7040
+ }
7041
+ if (parts.length > 0) {
7042
+ e.message = `${e.message}
7043
+
7044
+ Diagnostics:
7045
+ ${parts.join("\n")}`;
7046
+ }
7047
+ }
6866
7048
  }
6867
7049
  return e;
6868
7050
  }
@@ -6930,12 +7112,12 @@ class VmSnapshotsNamespace {
6930
7112
  const { spec: _spec, ...rest } = requestOptions;
6931
7113
  requestOptions = rest;
6932
7114
  }
6933
- if (isVmTemplateLike(requestOptions.template)) {
7115
+ if (isVmTemplateLike$1(requestOptions.template)) {
6934
7116
  const processedTemplate = await processTemplateTree(
6935
7117
  requestOptions.template
6936
7118
  );
6937
7119
  requestOptions.template = processedTemplate;
6938
- if (isVmTemplateLike(processedTemplate.raw.template)) {
7120
+ if (isVmTemplateLike$1(processedTemplate.raw.template)) {
6939
7121
  requestOptions.template = await ensureNestedTemplates(
6940
7122
  processedTemplate,
6941
7123
  this
@@ -6950,7 +7132,9 @@ class VmSnapshotsNamespace {
6950
7132
  return this.apiClient.post("/v1/vms/snapshots", {
6951
7133
  body: {
6952
7134
  ...requestOptions,
6953
- template: isVmTemplateLike(requestOptions.template) ? requestOptions.template.raw : requestOptions.template
7135
+ template: normalizeTemplateForRequest(
7136
+ isVmTemplateLike$1(requestOptions.template) ? requestOptions.template.raw : requestOptions.template
7137
+ )
6954
7138
  }
6955
7139
  }).catch((e) => {
6956
7140
  enhanceError(e);
@@ -6970,22 +7154,9 @@ async function processTemplateTree(template) {
6970
7154
  );
6971
7155
  }
6972
7156
  if (template.raw.systemd?.services) {
6973
- const normalizedServices = template.raw.systemd.services.map((service) => ({
6974
- ...service,
6975
- after: service.after?.map((s) => s.includes(".") ? s : `${s}.service`),
6976
- requires: service.requires?.map(
6977
- (s) => s.includes(".") ? s : `${s}.service`
6978
- ),
6979
- wantedBy: service.wantedBy?.map(
6980
- (s) => s.includes(".") ? s : `${s}.service`
6981
- )
6982
- }));
6983
- const { services: processedServices, additionalFiles: bashFiles } = processSystemdServices(
6984
- normalizedServices,
6985
- template.raw.additionalFiles ?? {}
7157
+ template.raw.systemd.services = normalizeSystemdServices(
7158
+ template.raw.systemd.services
6986
7159
  );
6987
- template.raw.systemd.services = processedServices;
6988
- template.raw.additionalFiles = bashFiles;
6989
7160
  }
6990
7161
  for (const key in template.with) {
6991
7162
  const builder = template.with[key];
@@ -7002,7 +7173,7 @@ async function processTemplateTree(template) {
7002
7173
  }
7003
7174
  if (builder.configureNestedTemplate) {
7004
7175
  let nestedTemplate;
7005
- if (isVmTemplateLike(template.raw.template)) {
7176
+ if (isVmTemplateLike$1(template.raw.template)) {
7006
7177
  nestedTemplate = template.raw.template;
7007
7178
  } else {
7008
7179
  nestedTemplate = new VmTemplate({});
@@ -7025,38 +7196,16 @@ async function processTemplateTree(template) {
7025
7196
  );
7026
7197
  }
7027
7198
  if (template.raw.systemd?.services) {
7028
- const normalizedServices = template.raw.systemd.services.map((service) => ({
7029
- ...service,
7030
- after: service.after?.map((s) => s.includes(".") ? s : `${s}.service`),
7031
- requires: service.requires?.map(
7032
- (s) => s.includes(".") ? s : `${s}.service`
7033
- ),
7034
- wantedBy: service.wantedBy?.map(
7035
- (s) => s.includes(".") ? s : `${s}.service`
7036
- )
7037
- }));
7038
- const { services: processedServices, additionalFiles: bashFiles } = processSystemdServices(
7039
- normalizedServices,
7040
- template.raw.additionalFiles ?? {}
7199
+ template.raw.systemd.services = normalizeSystemdServices(
7200
+ template.raw.systemd.services
7041
7201
  );
7042
- template.raw.systemd.services = processedServices;
7043
- template.raw.additionalFiles = bashFiles;
7044
7202
  }
7045
7203
  if (template.raw.systemd?.patchedServices) {
7046
- template.raw.systemd.patchedServices = template.raw.systemd.patchedServices.map((service) => ({
7047
- ...service,
7048
- after: service.after?.map(
7049
- (s) => s.includes(".") ? s : `${s}.service`
7050
- ),
7051
- requires: service.requires?.map(
7052
- (s) => s.includes(".") ? s : `${s}.service`
7053
- ),
7054
- wantedBy: service.wantedBy?.map(
7055
- (s) => s.includes(".") ? s : `${s}.service`
7056
- )
7057
- }));
7204
+ template.raw.systemd.patchedServices = normalizePatchedServices(
7205
+ template.raw.systemd.patchedServices
7206
+ );
7058
7207
  }
7059
- if (isVmTemplateLike(template.raw.template)) {
7208
+ if (isVmTemplateLike$1(template.raw.template)) {
7060
7209
  template.raw.template = await processTemplateTree(
7061
7210
  template.raw.template
7062
7211
  );
@@ -7065,7 +7214,7 @@ async function processTemplateTree(template) {
7065
7214
  }
7066
7215
  async function ensureNestedTemplates(template, snapshots) {
7067
7216
  const templates = [template];
7068
- while (isVmTemplateLike(templates.at(-1)?.raw.template)) {
7217
+ while (isVmTemplateLike$1(templates.at(-1)?.raw.template)) {
7069
7218
  const innerTemplate = templates.at(-1).raw.template;
7070
7219
  templates.at(-1).raw.template = void 0;
7071
7220
  templates.push(innerTemplate);
@@ -7250,7 +7399,7 @@ async function readFiles(directory) {
7250
7399
  }
7251
7400
 
7252
7401
  async function debugCreateRequests(freestyle, optionsOrSpec = {}) {
7253
- const options = isVmSpecLike(optionsOrSpec) ? { spec: optionsOrSpec } : optionsOrSpec;
7402
+ const options = isVmSpecLike(optionsOrSpec) ? { spec: cloneVmSpec(optionsOrSpec) } : cloneCreateOptions(optionsOrSpec);
7254
7403
  const requests = [];
7255
7404
  let snapshotCounter = 0;
7256
7405
  let vmCounter = 0;
@@ -7297,6 +7446,79 @@ async function debugCreateRequests(freestyle, optionsOrSpec = {}) {
7297
7446
  function isVmSpecLike(value) {
7298
7447
  return !!value && typeof value === "object" && "raw" in value && "with" in value;
7299
7448
  }
7449
+ function isVmTemplateLike(value) {
7450
+ return !!value && typeof value === "object" && "raw" in value && "with" in value;
7451
+ }
7452
+ function cloneCreateOptions(options) {
7453
+ return {
7454
+ ...clonePlainObject(options),
7455
+ with: options.with,
7456
+ template: isVmTemplateLike(options.template) ? cloneVmTemplate(options.template) : cloneValue(options.template),
7457
+ spec: isVmSpecLike(options.spec) ? cloneVmSpec(options.spec) : void 0,
7458
+ snapshot: isVmSpecLike(options.snapshot) ? cloneVmSpec(options.snapshot) : void 0
7459
+ };
7460
+ }
7461
+ function cloneVmSpec(spec) {
7462
+ const clonedRaw = cloneVmSpecRaw(spec.raw);
7463
+ return new VmSpec({
7464
+ ...clonedRaw,
7465
+ with: { ...spec.builders },
7466
+ __withDiscriminators: typeof spec.getBuilderDiscriminators === "function" ? spec.getBuilderDiscriminators() : void 0
7467
+ });
7468
+ }
7469
+ function cloneVmTemplate(template) {
7470
+ const clonedRaw = cloneVmTemplateRaw(template.raw);
7471
+ return new VmTemplate({
7472
+ ...clonedRaw,
7473
+ with: { ...template.with }
7474
+ });
7475
+ }
7476
+ function cloneVmSpecRaw(raw) {
7477
+ const cloned = cloneVmTemplateRaw(raw);
7478
+ if (isVmSpecLike(raw.snapshot)) {
7479
+ cloned.snapshot = cloneVmSpec(raw.snapshot);
7480
+ } else if (raw.snapshot !== void 0) {
7481
+ cloned.snapshot = cloneValue(raw.snapshot);
7482
+ }
7483
+ return cloned;
7484
+ }
7485
+ function cloneVmTemplateRaw(raw) {
7486
+ const cloned = clonePlainObject(raw);
7487
+ if (isVmTemplateLike(raw.template)) {
7488
+ cloned.template = cloneVmTemplate(raw.template);
7489
+ } else if (raw.template !== void 0) {
7490
+ cloned.template = cloneValue(raw.template);
7491
+ }
7492
+ return cloned;
7493
+ }
7494
+ function clonePlainObject(value) {
7495
+ if (!value || typeof value !== "object") {
7496
+ return value;
7497
+ }
7498
+ const cloned = {};
7499
+ for (const [key, entry] of Object.entries(value)) {
7500
+ cloned[key] = cloneValue(entry);
7501
+ }
7502
+ return cloned;
7503
+ }
7504
+ function cloneValue(value) {
7505
+ if (value instanceof VmBaseImage) {
7506
+ return new VmBaseImage(value.toRaw());
7507
+ }
7508
+ if (isVmSpecLike(value)) {
7509
+ return cloneVmSpec(value);
7510
+ }
7511
+ if (isVmTemplateLike(value)) {
7512
+ return cloneVmTemplate(value);
7513
+ }
7514
+ if (Array.isArray(value)) {
7515
+ return value.map((entry) => cloneValue(entry));
7516
+ }
7517
+ if (value && typeof value === "object") {
7518
+ return clonePlainObject(value);
7519
+ }
7520
+ return value;
7521
+ }
7300
7522
 
7301
7523
  class Freestyle {
7302
7524
  /**