freestyle-sandboxes 0.1.39 → 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.mjs CHANGED
@@ -6007,6 +6007,64 @@ function composeVmSpecs(specs) {
6007
6007
  }
6008
6008
 
6009
6009
  const DEFAULT_CONFIGURE_BASE_IMAGE = "FROM debian:trixie-slim";
6010
+ const RUN_COMMANDS_SYSTEMD_SERVICE_PREFIX = "freestyle-run-command";
6011
+ const WAIT_FOR_SYSTEMD_SERVICE_PREFIX = "freestyle-wait-for";
6012
+ function escapeRegExp(value) {
6013
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6014
+ }
6015
+ function getGeneratedServiceState(services, prefix) {
6016
+ const generatedPrefixes = [
6017
+ RUN_COMMANDS_SYSTEMD_SERVICE_PREFIX,
6018
+ WAIT_FOR_SYSTEMD_SERVICE_PREFIX
6019
+ ];
6020
+ let maxIndex = 0;
6021
+ let lastGeneratedServiceName;
6022
+ for (const service of services) {
6023
+ if (!service.name) {
6024
+ continue;
6025
+ }
6026
+ for (const generatedPrefix of generatedPrefixes) {
6027
+ const match = service.name.match(
6028
+ new RegExp(`^${escapeRegExp(generatedPrefix)}-(\\d+)$`)
6029
+ );
6030
+ if (!match) {
6031
+ continue;
6032
+ }
6033
+ if (generatedPrefix === prefix) {
6034
+ const index = Number.parseInt(match[1] ?? "0", 10);
6035
+ if (index > maxIndex) {
6036
+ maxIndex = index;
6037
+ }
6038
+ }
6039
+ lastGeneratedServiceName = service.name;
6040
+ break;
6041
+ }
6042
+ }
6043
+ return { maxIndex, lastGeneratedServiceName };
6044
+ }
6045
+ function appendServiceDependency(dependencies, dependency) {
6046
+ if (!dependency) {
6047
+ return dependencies ?? void 0;
6048
+ }
6049
+ const values = [...dependencies ?? []];
6050
+ if (!values.includes(dependency)) {
6051
+ values.push(dependency);
6052
+ }
6053
+ return values;
6054
+ }
6055
+ function buildWaitForScript(command, intervalSeconds) {
6056
+ const body = command.trim().split("\n").map((line) => ` ${line}`).join("\n");
6057
+ return [
6058
+ "while true; do",
6059
+ " if (",
6060
+ body,
6061
+ " ); then",
6062
+ " exit 0",
6063
+ " fi",
6064
+ ` sleep ${intervalSeconds}`,
6065
+ "done"
6066
+ ].join("\n");
6067
+ }
6010
6068
  function processSystemdServices(services, existingFiles = {}) {
6011
6069
  const additionalFiles = { ...existingFiles };
6012
6070
  const processedServices = [];
@@ -6041,6 +6099,63 @@ ${bash}`;
6041
6099
  }
6042
6100
  return { services: processedServices, additionalFiles };
6043
6101
  }
6102
+ function normalizeSystemdServices(services) {
6103
+ return services.map((service) => ({
6104
+ ...service,
6105
+ after: service.after?.map((s) => s.includes(".") ? s : `${s}.service`),
6106
+ requires: service.requires?.map(
6107
+ (s) => s.includes(".") ? s : `${s}.service`
6108
+ ),
6109
+ wantedBy: service.wantedBy?.map(
6110
+ (s) => s.includes(".") ? s : `${s}.service`
6111
+ )
6112
+ }));
6113
+ }
6114
+ function normalizePatchedServices(services) {
6115
+ return services.map((service) => ({
6116
+ ...service,
6117
+ after: service.after?.map((s) => s.includes(".") ? s : `${s}.service`),
6118
+ requires: service.requires?.map(
6119
+ (s) => s.includes(".") ? s : `${s}.service`
6120
+ ),
6121
+ wantedBy: service.wantedBy?.map(
6122
+ (s) => s.includes(".") ? s : `${s}.service`
6123
+ )
6124
+ }));
6125
+ }
6126
+ function normalizeTemplateForRequest(template) {
6127
+ const normalizedTemplate = {
6128
+ ...template ?? {}
6129
+ };
6130
+ if (normalizedTemplate.template) {
6131
+ normalizedTemplate.template = normalizeTemplateForRequest(
6132
+ normalizedTemplate.template
6133
+ );
6134
+ }
6135
+ if (normalizedTemplate.baseImage) {
6136
+ normalizedTemplate.baseImage = normalizeBaseImage(
6137
+ normalizedTemplate.baseImage
6138
+ )?.toRaw();
6139
+ }
6140
+ normalizedTemplate.git = normalizeGitOptions(normalizedTemplate.git);
6141
+ if (normalizedTemplate.systemd?.services) {
6142
+ const normalizedServices = normalizeSystemdServices(
6143
+ normalizedTemplate.systemd.services
6144
+ );
6145
+ const { services: processedServices, additionalFiles: bashFiles } = processSystemdServices(
6146
+ normalizedServices,
6147
+ normalizedTemplate.additionalFiles ?? {}
6148
+ );
6149
+ normalizedTemplate.systemd.services = processedServices;
6150
+ normalizedTemplate.additionalFiles = bashFiles;
6151
+ }
6152
+ if (normalizedTemplate.systemd?.patchedServices) {
6153
+ normalizedTemplate.systemd.patchedServices = normalizePatchedServices(
6154
+ normalizedTemplate.systemd.patchedServices
6155
+ );
6156
+ }
6157
+ return normalizedTemplate;
6158
+ }
6044
6159
  function normalizeGitOptions(git) {
6045
6160
  if (!git) return git;
6046
6161
  return {
@@ -6236,10 +6351,12 @@ class Vm {
6236
6351
  params: { vm_id: this.vmId },
6237
6352
  body: options
6238
6353
  });
6239
- const vmId = response.id;
6240
6354
  return {
6241
- vmId,
6242
- vm: new Vm({ vmId, freestyle: this._freestyle })
6355
+ forks: response.forks.map((fork) => ({
6356
+ vmId: fork.id,
6357
+ vm: this._freestyle.vms.ref({ vmId: fork.id })
6358
+ // Create a new Vm instance for the forked VM
6359
+ }))
6243
6360
  };
6244
6361
  }
6245
6362
  /**
@@ -6388,10 +6505,14 @@ class VmSpec {
6388
6505
  return { ...this.withDiscriminators };
6389
6506
  }
6390
6507
  mergeRaw(options) {
6508
+ const existingSnapshot = this.raw.snapshot;
6391
6509
  this.raw = composeCreateVmOptions([
6392
6510
  this.raw,
6393
6511
  options
6394
6512
  ]);
6513
+ if (existingSnapshot !== void 0 && this.raw.snapshot === void 0) {
6514
+ this.raw.snapshot = existingSnapshot;
6515
+ }
6395
6516
  return this;
6396
6517
  }
6397
6518
  clearBuilders() {
@@ -6475,9 +6596,74 @@ class VmSpec {
6475
6596
  return this;
6476
6597
  }
6477
6598
  runCommands(...commands) {
6478
- const image = normalizeBaseImage(this.raw.baseImage) ?? new VmBaseImage();
6479
- image.runCommands(...commands);
6480
- this.raw.baseImage = image;
6599
+ const normalizedCommands = commands.map((command) => command.trim()).filter((command) => command.length > 0);
6600
+ if (normalizedCommands.length === 0) {
6601
+ return this;
6602
+ }
6603
+ const existingSystemd = this.raw.systemd ?? {};
6604
+ const existingServices = [...existingSystemd.services ?? []];
6605
+ let { maxIndex: maxRunCommandIndex, lastGeneratedServiceName } = getGeneratedServiceState(
6606
+ existingServices,
6607
+ RUN_COMMANDS_SYSTEMD_SERVICE_PREFIX
6608
+ );
6609
+ for (const command of normalizedCommands) {
6610
+ const nextIndex = ++maxRunCommandIndex;
6611
+ const serviceName = `${RUN_COMMANDS_SYSTEMD_SERVICE_PREFIX}-${nextIndex}`;
6612
+ existingServices.push({
6613
+ name: serviceName,
6614
+ mode: "oneshot",
6615
+ deleteAfterSuccess: true,
6616
+ bash: command,
6617
+ ...lastGeneratedServiceName ? {
6618
+ after: [lastGeneratedServiceName],
6619
+ requires: [lastGeneratedServiceName]
6620
+ } : {}
6621
+ });
6622
+ lastGeneratedServiceName = serviceName;
6623
+ }
6624
+ this.raw.systemd = {
6625
+ ...existingSystemd,
6626
+ services: existingServices
6627
+ };
6628
+ return this;
6629
+ }
6630
+ waitFor(command, config = {}) {
6631
+ const trimmedCommand = command.trim();
6632
+ if (!trimmedCommand) {
6633
+ throw new Error("VmSpec.waitFor requires a non-empty command");
6634
+ }
6635
+ const intervalSeconds = config.intervalSeconds ?? 2;
6636
+ if (!Number.isFinite(intervalSeconds) || intervalSeconds <= 0) {
6637
+ throw new Error("VmSpec.waitFor intervalSeconds must be greater than 0");
6638
+ }
6639
+ const existingSystemd = this.raw.systemd ?? {};
6640
+ const existingServices = [...existingSystemd.services ?? []];
6641
+ const { maxIndex, lastGeneratedServiceName } = getGeneratedServiceState(
6642
+ existingServices,
6643
+ WAIT_FOR_SYSTEMD_SERVICE_PREFIX
6644
+ );
6645
+ const {
6646
+ intervalSeconds: _intervalSeconds,
6647
+ name,
6648
+ after,
6649
+ requires,
6650
+ timeoutSec,
6651
+ ...rest
6652
+ } = config;
6653
+ existingServices.push({
6654
+ ...rest,
6655
+ name: name ?? `${WAIT_FOR_SYSTEMD_SERVICE_PREFIX}-${maxIndex + 1}`,
6656
+ mode: "oneshot",
6657
+ // deleteAfterSuccess: true,
6658
+ timeoutSec: timeoutSec ?? 0,
6659
+ after: appendServiceDependency(after, lastGeneratedServiceName),
6660
+ requires: appendServiceDependency(requires, lastGeneratedServiceName),
6661
+ bash: buildWaitForScript(trimmedCommand, intervalSeconds)
6662
+ });
6663
+ this.raw.systemd = {
6664
+ ...existingSystemd,
6665
+ services: existingServices
6666
+ };
6481
6667
  return this;
6482
6668
  }
6483
6669
  repo(repo, path) {
@@ -6504,7 +6690,7 @@ class VmSpec {
6504
6690
  function isVmSpecLike$1(value) {
6505
6691
  return !!value && typeof value === "object" && "raw" in value && "with" in value;
6506
6692
  }
6507
- function isVmTemplateLike(value) {
6693
+ function isVmTemplateLike$1(value) {
6508
6694
  return !!value && typeof value === "object" && "raw" in value && "with" in value;
6509
6695
  }
6510
6696
  async function convertSpecSnapshotsToTemplates(spec, processBuilders = true) {
@@ -6630,21 +6816,8 @@ class VmsNamespace {
6630
6816
  }
6631
6817
  }
6632
6818
  if (options.systemd?.services) {
6633
- const normalizedServices = options.systemd.services.map(
6634
- (service) => {
6635
- return {
6636
- ...service,
6637
- after: service.after?.map(
6638
- (s) => s.includes(".") ? s : `${s}.service`
6639
- ),
6640
- requires: service.requires?.map(
6641
- (s) => s.includes(".") ? s : `${s}.service`
6642
- ),
6643
- wantedBy: service.wantedBy?.map(
6644
- (s) => s.includes(".") ? s : `${s}.service`
6645
- )
6646
- };
6647
- }
6819
+ const normalizedServices = normalizeSystemdServices(
6820
+ options.systemd.services
6648
6821
  );
6649
6822
  const { services: processedServices, additionalFiles: bashFiles } = processSystemdServices(
6650
6823
  normalizedServices,
@@ -6654,19 +6827,8 @@ class VmsNamespace {
6654
6827
  options.additionalFiles = bashFiles;
6655
6828
  }
6656
6829
  if (options.systemd?.patchedServices) {
6657
- options.systemd.patchedServices = options.systemd.patchedServices.map(
6658
- (service) => {
6659
- service.after = service.after?.map(
6660
- (s) => s.includes(".") ? s : `${s}.service`
6661
- );
6662
- service.requires = service.requires?.map(
6663
- (s) => s.includes(".") ? s : `${s}.service`
6664
- );
6665
- service.wantedBy = service.wantedBy?.map(
6666
- (s) => s.includes(".") ? s : `${s}.service`
6667
- );
6668
- return service;
6669
- }
6830
+ options.systemd.patchedServices = normalizePatchedServices(
6831
+ options.systemd.patchedServices
6670
6832
  );
6671
6833
  }
6672
6834
  if ("snapshot" in options) {
@@ -6699,7 +6861,7 @@ class VmsNamespace {
6699
6861
  const specBuilders = isVmSpecLike$1(
6700
6862
  options.spec
6701
6863
  ) ? collectSpecBuilders(options.spec) : void 0;
6702
- const templateBuilders = isVmTemplateLike(options.template) ? options.template.with : void 0;
6864
+ const templateBuilders = isVmTemplateLike$1(options.template) ? options.template.with : void 0;
6703
6865
  const builders = {
6704
6866
  ...templateBuilders || {},
6705
6867
  ...specBuilders || {},
@@ -6707,10 +6869,10 @@ class VmsNamespace {
6707
6869
  };
6708
6870
  const { with: _, spec: _spec, ...baseConfig } = options;
6709
6871
  let config = baseConfig;
6710
- if (isVmTemplateLike(config.template)) {
6872
+ if (isVmTemplateLike$1(config.template)) {
6711
6873
  config.template = await processTemplateTree(config.template);
6712
6874
  }
6713
- if (isVmTemplateLike(config.template)) {
6875
+ if (isVmTemplateLike$1(config.template)) {
6714
6876
  config.template = await ensureNestedTemplates(
6715
6877
  config.template,
6716
6878
  this.snapshots
@@ -6724,18 +6886,9 @@ class VmsNamespace {
6724
6886
  }
6725
6887
  }
6726
6888
  if (config.systemd?.services) {
6727
- const normalizedServices = config.systemd.services.map((service) => ({
6728
- ...service,
6729
- after: service.after?.map(
6730
- (s) => s.includes(".") ? s : `${s}.service`
6731
- ),
6732
- requires: service.requires?.map(
6733
- (s) => s.includes(".") ? s : `${s}.service`
6734
- ),
6735
- wantedBy: service.wantedBy?.map(
6736
- (s) => s.includes(".") ? s : `${s}.service`
6737
- )
6738
- }));
6889
+ const normalizedServices = normalizeSystemdServices(
6890
+ config.systemd.services
6891
+ );
6739
6892
  const { services: processedServices, additionalFiles: bashFiles } = processSystemdServices(
6740
6893
  normalizedServices,
6741
6894
  config.additionalFiles ?? {}
@@ -6744,28 +6897,20 @@ class VmsNamespace {
6744
6897
  config.additionalFiles = bashFiles;
6745
6898
  }
6746
6899
  if (config.systemd?.patchedServices) {
6747
- config.systemd.patchedServices = config.systemd.patchedServices.map(
6748
- (service) => {
6749
- service.after = service.after?.map(
6750
- (s) => s.includes(".") ? s : `${s}.service`
6751
- );
6752
- service.requires = service.requires?.map(
6753
- (s) => s.includes(".") ? s : `${s}.service`
6754
- );
6755
- service.wantedBy = service.wantedBy?.map(
6756
- (s) => s.includes(".") ? s : `${s}.service`
6757
- );
6758
- return service;
6759
- }
6900
+ config.systemd.patchedServices = normalizePatchedServices(
6901
+ config.systemd.patchedServices
6760
6902
  );
6761
6903
  }
6762
6904
  config.git = normalizeGitOptions(config.git);
6763
6905
  const serializedBaseImage = normalizeBaseImage(config.baseImage)?.toRaw();
6764
- const rawTemplate = isVmTemplateLike(config.template) ? config.template.raw : config.template;
6906
+ const rawTemplate = isVmTemplateLike$1(config.template) ? config.template.raw : config.template;
6765
6907
  const requestTemplate = serializedBaseImage ? {
6766
6908
  ...rawTemplate ?? {},
6767
6909
  baseImage: serializedBaseImage
6768
6910
  } : rawTemplate;
6911
+ const normalizedRequestTemplate = requestTemplate ? normalizeTemplateForRequest(
6912
+ requestTemplate
6913
+ ) : requestTemplate;
6769
6914
  const {
6770
6915
  baseImage: _baseImage,
6771
6916
  template: _template,
@@ -6774,7 +6919,7 @@ class VmsNamespace {
6774
6919
  const response = await this.freestyle._apiClient.post("/v1/vms", {
6775
6920
  body: {
6776
6921
  ...requestConfig,
6777
- template: requestTemplate,
6922
+ template: normalizedRequestTemplate,
6778
6923
  // Cast systemd since we've processed SystemdServiceInput[] to RawSystemdService[]
6779
6924
  systemd: config.systemd,
6780
6925
  // Normalize git options - default config to {}
@@ -6869,6 +7014,35 @@ function enhanceError(e) {
6869
7014
  if (!e.message.includes("create --snapshot")) {
6870
7015
  e.message = `${e.message}. Hint: use \`npx freestyle-sandboxes@latest vm create --snapshot ${e.body.snapshotId} --ssh --delete\` to debug.`;
6871
7016
  }
7017
+ if (e.body.diagnostics) {
7018
+ const d = e.body.diagnostics;
7019
+ const parts = [];
7020
+ if (d.serviceStates.length > 0) {
7021
+ parts.push("Service states:");
7022
+ for (const g of d.serviceStates) {
7023
+ parts.push(
7024
+ ` ${g.activeState}(${g.subState}): ${g.services.join(", ")}`
7025
+ );
7026
+ }
7027
+ }
7028
+ const allLogs = [
7029
+ ...d.failedServiceLogs ?? [],
7030
+ ...d.additionalFailedServiceLogs ?? []
7031
+ ];
7032
+ if (allLogs.length > 0) {
7033
+ parts.push("Failed service logs:");
7034
+ for (const l of allLogs) {
7035
+ parts.push(` --- ${l.unitName} ---`);
7036
+ parts.push(` ${l.log}`);
7037
+ }
7038
+ }
7039
+ if (parts.length > 0) {
7040
+ e.message = `${e.message}
7041
+
7042
+ Diagnostics:
7043
+ ${parts.join("\n")}`;
7044
+ }
7045
+ }
6872
7046
  }
6873
7047
  return e;
6874
7048
  }
@@ -6936,12 +7110,12 @@ class VmSnapshotsNamespace {
6936
7110
  const { spec: _spec, ...rest } = requestOptions;
6937
7111
  requestOptions = rest;
6938
7112
  }
6939
- if (isVmTemplateLike(requestOptions.template)) {
7113
+ if (isVmTemplateLike$1(requestOptions.template)) {
6940
7114
  const processedTemplate = await processTemplateTree(
6941
7115
  requestOptions.template
6942
7116
  );
6943
7117
  requestOptions.template = processedTemplate;
6944
- if (isVmTemplateLike(processedTemplate.raw.template)) {
7118
+ if (isVmTemplateLike$1(processedTemplate.raw.template)) {
6945
7119
  requestOptions.template = await ensureNestedTemplates(
6946
7120
  processedTemplate,
6947
7121
  this
@@ -6956,7 +7130,9 @@ class VmSnapshotsNamespace {
6956
7130
  return this.apiClient.post("/v1/vms/snapshots", {
6957
7131
  body: {
6958
7132
  ...requestOptions,
6959
- template: isVmTemplateLike(requestOptions.template) ? requestOptions.template.raw : requestOptions.template
7133
+ template: normalizeTemplateForRequest(
7134
+ isVmTemplateLike$1(requestOptions.template) ? requestOptions.template.raw : requestOptions.template
7135
+ )
6960
7136
  }
6961
7137
  }).catch((e) => {
6962
7138
  enhanceError(e);
@@ -6976,22 +7152,9 @@ async function processTemplateTree(template) {
6976
7152
  );
6977
7153
  }
6978
7154
  if (template.raw.systemd?.services) {
6979
- const normalizedServices = template.raw.systemd.services.map((service) => ({
6980
- ...service,
6981
- after: service.after?.map((s) => s.includes(".") ? s : `${s}.service`),
6982
- requires: service.requires?.map(
6983
- (s) => s.includes(".") ? s : `${s}.service`
6984
- ),
6985
- wantedBy: service.wantedBy?.map(
6986
- (s) => s.includes(".") ? s : `${s}.service`
6987
- )
6988
- }));
6989
- const { services: processedServices, additionalFiles: bashFiles } = processSystemdServices(
6990
- normalizedServices,
6991
- template.raw.additionalFiles ?? {}
7155
+ template.raw.systemd.services = normalizeSystemdServices(
7156
+ template.raw.systemd.services
6992
7157
  );
6993
- template.raw.systemd.services = processedServices;
6994
- template.raw.additionalFiles = bashFiles;
6995
7158
  }
6996
7159
  for (const key in template.with) {
6997
7160
  const builder = template.with[key];
@@ -7008,7 +7171,7 @@ async function processTemplateTree(template) {
7008
7171
  }
7009
7172
  if (builder.configureNestedTemplate) {
7010
7173
  let nestedTemplate;
7011
- if (isVmTemplateLike(template.raw.template)) {
7174
+ if (isVmTemplateLike$1(template.raw.template)) {
7012
7175
  nestedTemplate = template.raw.template;
7013
7176
  } else {
7014
7177
  nestedTemplate = new VmTemplate({});
@@ -7031,38 +7194,16 @@ async function processTemplateTree(template) {
7031
7194
  );
7032
7195
  }
7033
7196
  if (template.raw.systemd?.services) {
7034
- const normalizedServices = template.raw.systemd.services.map((service) => ({
7035
- ...service,
7036
- after: service.after?.map((s) => s.includes(".") ? s : `${s}.service`),
7037
- requires: service.requires?.map(
7038
- (s) => s.includes(".") ? s : `${s}.service`
7039
- ),
7040
- wantedBy: service.wantedBy?.map(
7041
- (s) => s.includes(".") ? s : `${s}.service`
7042
- )
7043
- }));
7044
- const { services: processedServices, additionalFiles: bashFiles } = processSystemdServices(
7045
- normalizedServices,
7046
- template.raw.additionalFiles ?? {}
7197
+ template.raw.systemd.services = normalizeSystemdServices(
7198
+ template.raw.systemd.services
7047
7199
  );
7048
- template.raw.systemd.services = processedServices;
7049
- template.raw.additionalFiles = bashFiles;
7050
7200
  }
7051
7201
  if (template.raw.systemd?.patchedServices) {
7052
- template.raw.systemd.patchedServices = template.raw.systemd.patchedServices.map((service) => ({
7053
- ...service,
7054
- after: service.after?.map(
7055
- (s) => s.includes(".") ? s : `${s}.service`
7056
- ),
7057
- requires: service.requires?.map(
7058
- (s) => s.includes(".") ? s : `${s}.service`
7059
- ),
7060
- wantedBy: service.wantedBy?.map(
7061
- (s) => s.includes(".") ? s : `${s}.service`
7062
- )
7063
- }));
7202
+ template.raw.systemd.patchedServices = normalizePatchedServices(
7203
+ template.raw.systemd.patchedServices
7204
+ );
7064
7205
  }
7065
- if (isVmTemplateLike(template.raw.template)) {
7206
+ if (isVmTemplateLike$1(template.raw.template)) {
7066
7207
  template.raw.template = await processTemplateTree(
7067
7208
  template.raw.template
7068
7209
  );
@@ -7071,7 +7212,7 @@ async function processTemplateTree(template) {
7071
7212
  }
7072
7213
  async function ensureNestedTemplates(template, snapshots) {
7073
7214
  const templates = [template];
7074
- while (isVmTemplateLike(templates.at(-1)?.raw.template)) {
7215
+ while (isVmTemplateLike$1(templates.at(-1)?.raw.template)) {
7075
7216
  const innerTemplate = templates.at(-1).raw.template;
7076
7217
  templates.at(-1).raw.template = void 0;
7077
7218
  templates.push(innerTemplate);
@@ -7256,7 +7397,7 @@ async function readFiles(directory) {
7256
7397
  }
7257
7398
 
7258
7399
  async function debugCreateRequests(freestyle, optionsOrSpec = {}) {
7259
- const options = isVmSpecLike(optionsOrSpec) ? { spec: optionsOrSpec } : optionsOrSpec;
7400
+ const options = isVmSpecLike(optionsOrSpec) ? { spec: cloneVmSpec(optionsOrSpec) } : cloneCreateOptions(optionsOrSpec);
7260
7401
  const requests = [];
7261
7402
  let snapshotCounter = 0;
7262
7403
  let vmCounter = 0;
@@ -7303,6 +7444,79 @@ async function debugCreateRequests(freestyle, optionsOrSpec = {}) {
7303
7444
  function isVmSpecLike(value) {
7304
7445
  return !!value && typeof value === "object" && "raw" in value && "with" in value;
7305
7446
  }
7447
+ function isVmTemplateLike(value) {
7448
+ return !!value && typeof value === "object" && "raw" in value && "with" in value;
7449
+ }
7450
+ function cloneCreateOptions(options) {
7451
+ return {
7452
+ ...clonePlainObject(options),
7453
+ with: options.with,
7454
+ template: isVmTemplateLike(options.template) ? cloneVmTemplate(options.template) : cloneValue(options.template),
7455
+ spec: isVmSpecLike(options.spec) ? cloneVmSpec(options.spec) : void 0,
7456
+ snapshot: isVmSpecLike(options.snapshot) ? cloneVmSpec(options.snapshot) : void 0
7457
+ };
7458
+ }
7459
+ function cloneVmSpec(spec) {
7460
+ const clonedRaw = cloneVmSpecRaw(spec.raw);
7461
+ return new VmSpec({
7462
+ ...clonedRaw,
7463
+ with: { ...spec.builders },
7464
+ __withDiscriminators: typeof spec.getBuilderDiscriminators === "function" ? spec.getBuilderDiscriminators() : void 0
7465
+ });
7466
+ }
7467
+ function cloneVmTemplate(template) {
7468
+ const clonedRaw = cloneVmTemplateRaw(template.raw);
7469
+ return new VmTemplate({
7470
+ ...clonedRaw,
7471
+ with: { ...template.with }
7472
+ });
7473
+ }
7474
+ function cloneVmSpecRaw(raw) {
7475
+ const cloned = cloneVmTemplateRaw(raw);
7476
+ if (isVmSpecLike(raw.snapshot)) {
7477
+ cloned.snapshot = cloneVmSpec(raw.snapshot);
7478
+ } else if (raw.snapshot !== void 0) {
7479
+ cloned.snapshot = cloneValue(raw.snapshot);
7480
+ }
7481
+ return cloned;
7482
+ }
7483
+ function cloneVmTemplateRaw(raw) {
7484
+ const cloned = clonePlainObject(raw);
7485
+ if (isVmTemplateLike(raw.template)) {
7486
+ cloned.template = cloneVmTemplate(raw.template);
7487
+ } else if (raw.template !== void 0) {
7488
+ cloned.template = cloneValue(raw.template);
7489
+ }
7490
+ return cloned;
7491
+ }
7492
+ function clonePlainObject(value) {
7493
+ if (!value || typeof value !== "object") {
7494
+ return value;
7495
+ }
7496
+ const cloned = {};
7497
+ for (const [key, entry] of Object.entries(value)) {
7498
+ cloned[key] = cloneValue(entry);
7499
+ }
7500
+ return cloned;
7501
+ }
7502
+ function cloneValue(value) {
7503
+ if (value instanceof VmBaseImage) {
7504
+ return new VmBaseImage(value.toRaw());
7505
+ }
7506
+ if (isVmSpecLike(value)) {
7507
+ return cloneVmSpec(value);
7508
+ }
7509
+ if (isVmTemplateLike(value)) {
7510
+ return cloneVmTemplate(value);
7511
+ }
7512
+ if (Array.isArray(value)) {
7513
+ return value.map((entry) => cloneValue(entry));
7514
+ }
7515
+ if (value && typeof value === "object") {
7516
+ return clonePlainObject(value);
7517
+ }
7518
+ return value;
7519
+ }
7306
7520
 
7307
7521
  class Freestyle {
7308
7522
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "freestyle-sandboxes",
3
- "version": "0.1.39",
3
+ "version": "0.1.40",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "require": {