freestyle-sandboxes 0.1.39 → 0.1.41

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,9 +6690,76 @@ 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
  }
6696
+ function cloneVmSpecTree(spec) {
6697
+ const clonedRaw = cloneVmSpecRaw$1(spec.raw);
6698
+ return new VmSpec({
6699
+ ...clonedRaw,
6700
+ with: { ...spec.builders },
6701
+ __withDiscriminators: spec.getBuilderDiscriminators()
6702
+ });
6703
+ }
6704
+ function isVmSpecInstance(value) {
6705
+ return value instanceof VmSpec;
6706
+ }
6707
+ function isVmTemplateInstance(value) {
6708
+ return value instanceof VmTemplate;
6709
+ }
6710
+ function cloneVmTemplateTree(template) {
6711
+ const clonedRaw = cloneVmTemplateRaw$1(template.raw);
6712
+ return new VmTemplate({
6713
+ ...clonedRaw,
6714
+ with: { ...template.with }
6715
+ });
6716
+ }
6717
+ function cloneVmSpecRaw$1(raw) {
6718
+ const cloned = cloneVmTemplateRaw$1(raw);
6719
+ if (isVmSpecInstance(raw.snapshot)) {
6720
+ cloned.snapshot = cloneVmSpecTree(raw.snapshot);
6721
+ } else if (raw.snapshot !== void 0) {
6722
+ cloned.snapshot = cloneVmValue(raw.snapshot);
6723
+ }
6724
+ return cloned;
6725
+ }
6726
+ function cloneVmTemplateRaw$1(raw) {
6727
+ const cloned = cloneVmPlainObject(raw);
6728
+ if (isVmTemplateInstance(raw.template)) {
6729
+ cloned.template = cloneVmTemplateTree(raw.template);
6730
+ } else if (raw.template !== void 0) {
6731
+ cloned.template = cloneVmValue(raw.template);
6732
+ }
6733
+ return cloned;
6734
+ }
6735
+ function cloneVmPlainObject(value) {
6736
+ if (!value || typeof value !== "object") {
6737
+ return value;
6738
+ }
6739
+ const cloned = {};
6740
+ for (const [key, entry] of Object.entries(value)) {
6741
+ cloned[key] = cloneVmValue(entry);
6742
+ }
6743
+ return cloned;
6744
+ }
6745
+ function cloneVmValue(value) {
6746
+ if (value instanceof VmBaseImage) {
6747
+ return new VmBaseImage(value.toRaw());
6748
+ }
6749
+ if (isVmSpecInstance(value)) {
6750
+ return cloneVmSpecTree(value);
6751
+ }
6752
+ if (isVmTemplateInstance(value)) {
6753
+ return cloneVmTemplateTree(value);
6754
+ }
6755
+ if (Array.isArray(value)) {
6756
+ return value.map((entry) => cloneVmValue(entry));
6757
+ }
6758
+ if (value && typeof value === "object") {
6759
+ return cloneVmPlainObject(value);
6760
+ }
6761
+ return value;
6762
+ }
6510
6763
  async function convertSpecSnapshotsToTemplates(spec, processBuilders = true) {
6511
6764
  if (!isVmSpecLike$1(spec.raw.snapshot)) {
6512
6765
  return void 0;
@@ -6618,7 +6871,17 @@ class VmsNamespace {
6618
6871
  snapshots;
6619
6872
  async create(options = {}) {
6620
6873
  if (isVmSpecLike$1(options)) {
6621
- options = { spec: options };
6874
+ options = { spec: cloneVmSpecTree(options) };
6875
+ } else {
6876
+ if (isVmSpecLike$1(options.spec)) {
6877
+ options.spec = cloneVmSpecTree(options.spec);
6878
+ }
6879
+ if (isVmSpecLike$1(options.snapshot)) {
6880
+ options.snapshot = cloneVmSpecTree(options.snapshot);
6881
+ }
6882
+ if (isVmTemplateLike$1(options.template)) {
6883
+ options.template = cloneVmTemplateTree(options.template);
6884
+ }
6622
6885
  }
6623
6886
  if (isVmSpecLike$1(options.snapshot)) {
6624
6887
  if (isVmSpecLike$1(options.spec)) {
@@ -6630,21 +6893,8 @@ class VmsNamespace {
6630
6893
  }
6631
6894
  }
6632
6895
  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
- }
6896
+ const normalizedServices = normalizeSystemdServices(
6897
+ options.systemd.services
6648
6898
  );
6649
6899
  const { services: processedServices, additionalFiles: bashFiles } = processSystemdServices(
6650
6900
  normalizedServices,
@@ -6654,19 +6904,8 @@ class VmsNamespace {
6654
6904
  options.additionalFiles = bashFiles;
6655
6905
  }
6656
6906
  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
- }
6907
+ options.systemd.patchedServices = normalizePatchedServices(
6908
+ options.systemd.patchedServices
6670
6909
  );
6671
6910
  }
6672
6911
  if ("snapshot" in options) {
@@ -6699,7 +6938,7 @@ class VmsNamespace {
6699
6938
  const specBuilders = isVmSpecLike$1(
6700
6939
  options.spec
6701
6940
  ) ? collectSpecBuilders(options.spec) : void 0;
6702
- const templateBuilders = isVmTemplateLike(options.template) ? options.template.with : void 0;
6941
+ const templateBuilders = isVmTemplateLike$1(options.template) ? options.template.with : void 0;
6703
6942
  const builders = {
6704
6943
  ...templateBuilders || {},
6705
6944
  ...specBuilders || {},
@@ -6707,10 +6946,10 @@ class VmsNamespace {
6707
6946
  };
6708
6947
  const { with: _, spec: _spec, ...baseConfig } = options;
6709
6948
  let config = baseConfig;
6710
- if (isVmTemplateLike(config.template)) {
6949
+ if (isVmTemplateLike$1(config.template)) {
6711
6950
  config.template = await processTemplateTree(config.template);
6712
6951
  }
6713
- if (isVmTemplateLike(config.template)) {
6952
+ if (isVmTemplateLike$1(config.template)) {
6714
6953
  config.template = await ensureNestedTemplates(
6715
6954
  config.template,
6716
6955
  this.snapshots
@@ -6724,18 +6963,9 @@ class VmsNamespace {
6724
6963
  }
6725
6964
  }
6726
6965
  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
- }));
6966
+ const normalizedServices = normalizeSystemdServices(
6967
+ config.systemd.services
6968
+ );
6739
6969
  const { services: processedServices, additionalFiles: bashFiles } = processSystemdServices(
6740
6970
  normalizedServices,
6741
6971
  config.additionalFiles ?? {}
@@ -6744,28 +6974,20 @@ class VmsNamespace {
6744
6974
  config.additionalFiles = bashFiles;
6745
6975
  }
6746
6976
  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
- }
6977
+ config.systemd.patchedServices = normalizePatchedServices(
6978
+ config.systemd.patchedServices
6760
6979
  );
6761
6980
  }
6762
6981
  config.git = normalizeGitOptions(config.git);
6763
6982
  const serializedBaseImage = normalizeBaseImage(config.baseImage)?.toRaw();
6764
- const rawTemplate = isVmTemplateLike(config.template) ? config.template.raw : config.template;
6983
+ const rawTemplate = isVmTemplateLike$1(config.template) ? config.template.raw : config.template;
6765
6984
  const requestTemplate = serializedBaseImage ? {
6766
6985
  ...rawTemplate ?? {},
6767
6986
  baseImage: serializedBaseImage
6768
6987
  } : rawTemplate;
6988
+ const normalizedRequestTemplate = requestTemplate ? normalizeTemplateForRequest(
6989
+ requestTemplate
6990
+ ) : requestTemplate;
6769
6991
  const {
6770
6992
  baseImage: _baseImage,
6771
6993
  template: _template,
@@ -6774,7 +6996,7 @@ class VmsNamespace {
6774
6996
  const response = await this.freestyle._apiClient.post("/v1/vms", {
6775
6997
  body: {
6776
6998
  ...requestConfig,
6777
- template: requestTemplate,
6999
+ template: normalizedRequestTemplate,
6778
7000
  // Cast systemd since we've processed SystemdServiceInput[] to RawSystemdService[]
6779
7001
  systemd: config.systemd,
6780
7002
  // Normalize git options - default config to {}
@@ -6869,6 +7091,35 @@ function enhanceError(e) {
6869
7091
  if (!e.message.includes("create --snapshot")) {
6870
7092
  e.message = `${e.message}. Hint: use \`npx freestyle-sandboxes@latest vm create --snapshot ${e.body.snapshotId} --ssh --delete\` to debug.`;
6871
7093
  }
7094
+ if (e.body.diagnostics) {
7095
+ const d = e.body.diagnostics;
7096
+ const parts = [];
7097
+ if (d.serviceStates.length > 0) {
7098
+ parts.push("Service states:");
7099
+ for (const g of d.serviceStates) {
7100
+ parts.push(
7101
+ ` ${g.activeState}(${g.subState}): ${g.services.join(", ")}`
7102
+ );
7103
+ }
7104
+ }
7105
+ const allLogs = [
7106
+ ...d.failedServiceLogs ?? [],
7107
+ ...d.additionalFailedServiceLogs ?? []
7108
+ ];
7109
+ if (allLogs.length > 0) {
7110
+ parts.push("Failed service logs:");
7111
+ for (const l of allLogs) {
7112
+ parts.push(` --- ${l.unitName} ---`);
7113
+ parts.push(` ${l.log}`);
7114
+ }
7115
+ }
7116
+ if (parts.length > 0) {
7117
+ e.message = `${e.message}
7118
+
7119
+ Diagnostics:
7120
+ ${parts.join("\n")}`;
7121
+ }
7122
+ }
6872
7123
  }
6873
7124
  return e;
6874
7125
  }
@@ -6907,7 +7158,12 @@ class VmSnapshotsNamespace {
6907
7158
  this.apiClient = apiClient;
6908
7159
  }
6909
7160
  async ensure(options) {
6910
- let requestOptions = options;
7161
+ let requestOptions = {
7162
+ ...options,
7163
+ spec: isVmSpecLike$1(options.spec) ? cloneVmSpecTree(options.spec) : void 0,
7164
+ snapshot: isVmSpecLike$1(options.snapshot) ? cloneVmSpecTree(options.snapshot) : void 0,
7165
+ template: isVmTemplateLike$1(options.template) ? cloneVmTemplateTree(options.template) : options.template
7166
+ };
6911
7167
  if (isVmSpecLike$1(requestOptions.snapshot)) {
6912
7168
  if (isVmSpecLike$1(requestOptions.spec)) {
6913
7169
  if (!requestOptions.spec.raw.snapshot) {
@@ -6936,12 +7192,12 @@ class VmSnapshotsNamespace {
6936
7192
  const { spec: _spec, ...rest } = requestOptions;
6937
7193
  requestOptions = rest;
6938
7194
  }
6939
- if (isVmTemplateLike(requestOptions.template)) {
7195
+ if (isVmTemplateLike$1(requestOptions.template)) {
6940
7196
  const processedTemplate = await processTemplateTree(
6941
7197
  requestOptions.template
6942
7198
  );
6943
7199
  requestOptions.template = processedTemplate;
6944
- if (isVmTemplateLike(processedTemplate.raw.template)) {
7200
+ if (isVmTemplateLike$1(processedTemplate.raw.template)) {
6945
7201
  requestOptions.template = await ensureNestedTemplates(
6946
7202
  processedTemplate,
6947
7203
  this
@@ -6956,7 +7212,9 @@ class VmSnapshotsNamespace {
6956
7212
  return this.apiClient.post("/v1/vms/snapshots", {
6957
7213
  body: {
6958
7214
  ...requestOptions,
6959
- template: isVmTemplateLike(requestOptions.template) ? requestOptions.template.raw : requestOptions.template
7215
+ template: normalizeTemplateForRequest(
7216
+ isVmTemplateLike$1(requestOptions.template) ? requestOptions.template.raw : requestOptions.template
7217
+ )
6960
7218
  }
6961
7219
  }).catch((e) => {
6962
7220
  enhanceError(e);
@@ -6976,22 +7234,9 @@ async function processTemplateTree(template) {
6976
7234
  );
6977
7235
  }
6978
7236
  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 ?? {}
7237
+ template.raw.systemd.services = normalizeSystemdServices(
7238
+ template.raw.systemd.services
6992
7239
  );
6993
- template.raw.systemd.services = processedServices;
6994
- template.raw.additionalFiles = bashFiles;
6995
7240
  }
6996
7241
  for (const key in template.with) {
6997
7242
  const builder = template.with[key];
@@ -7008,7 +7253,7 @@ async function processTemplateTree(template) {
7008
7253
  }
7009
7254
  if (builder.configureNestedTemplate) {
7010
7255
  let nestedTemplate;
7011
- if (isVmTemplateLike(template.raw.template)) {
7256
+ if (isVmTemplateLike$1(template.raw.template)) {
7012
7257
  nestedTemplate = template.raw.template;
7013
7258
  } else {
7014
7259
  nestedTemplate = new VmTemplate({});
@@ -7031,38 +7276,16 @@ async function processTemplateTree(template) {
7031
7276
  );
7032
7277
  }
7033
7278
  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 ?? {}
7279
+ template.raw.systemd.services = normalizeSystemdServices(
7280
+ template.raw.systemd.services
7047
7281
  );
7048
- template.raw.systemd.services = processedServices;
7049
- template.raw.additionalFiles = bashFiles;
7050
7282
  }
7051
7283
  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
- }));
7284
+ template.raw.systemd.patchedServices = normalizePatchedServices(
7285
+ template.raw.systemd.patchedServices
7286
+ );
7064
7287
  }
7065
- if (isVmTemplateLike(template.raw.template)) {
7288
+ if (isVmTemplateLike$1(template.raw.template)) {
7066
7289
  template.raw.template = await processTemplateTree(
7067
7290
  template.raw.template
7068
7291
  );
@@ -7071,7 +7294,7 @@ async function processTemplateTree(template) {
7071
7294
  }
7072
7295
  async function ensureNestedTemplates(template, snapshots) {
7073
7296
  const templates = [template];
7074
- while (isVmTemplateLike(templates.at(-1)?.raw.template)) {
7297
+ while (isVmTemplateLike$1(templates.at(-1)?.raw.template)) {
7075
7298
  const innerTemplate = templates.at(-1).raw.template;
7076
7299
  templates.at(-1).raw.template = void 0;
7077
7300
  templates.push(innerTemplate);
@@ -7256,7 +7479,7 @@ async function readFiles(directory) {
7256
7479
  }
7257
7480
 
7258
7481
  async function debugCreateRequests(freestyle, optionsOrSpec = {}) {
7259
- const options = isVmSpecLike(optionsOrSpec) ? { spec: optionsOrSpec } : optionsOrSpec;
7482
+ const options = isVmSpecLike(optionsOrSpec) ? { spec: cloneVmSpec(optionsOrSpec) } : cloneCreateOptions(optionsOrSpec);
7260
7483
  const requests = [];
7261
7484
  let snapshotCounter = 0;
7262
7485
  let vmCounter = 0;
@@ -7303,6 +7526,79 @@ async function debugCreateRequests(freestyle, optionsOrSpec = {}) {
7303
7526
  function isVmSpecLike(value) {
7304
7527
  return !!value && typeof value === "object" && "raw" in value && "with" in value;
7305
7528
  }
7529
+ function isVmTemplateLike(value) {
7530
+ return !!value && typeof value === "object" && "raw" in value && "with" in value;
7531
+ }
7532
+ function cloneCreateOptions(options) {
7533
+ return {
7534
+ ...clonePlainObject(options),
7535
+ with: options.with,
7536
+ template: isVmTemplateLike(options.template) ? cloneVmTemplate(options.template) : cloneValue(options.template),
7537
+ spec: isVmSpecLike(options.spec) ? cloneVmSpec(options.spec) : void 0,
7538
+ snapshot: isVmSpecLike(options.snapshot) ? cloneVmSpec(options.snapshot) : void 0
7539
+ };
7540
+ }
7541
+ function cloneVmSpec(spec) {
7542
+ const clonedRaw = cloneVmSpecRaw(spec.raw);
7543
+ return new VmSpec({
7544
+ ...clonedRaw,
7545
+ with: { ...spec.builders },
7546
+ __withDiscriminators: typeof spec.getBuilderDiscriminators === "function" ? spec.getBuilderDiscriminators() : void 0
7547
+ });
7548
+ }
7549
+ function cloneVmTemplate(template) {
7550
+ const clonedRaw = cloneVmTemplateRaw(template.raw);
7551
+ return new VmTemplate({
7552
+ ...clonedRaw,
7553
+ with: { ...template.with }
7554
+ });
7555
+ }
7556
+ function cloneVmSpecRaw(raw) {
7557
+ const cloned = cloneVmTemplateRaw(raw);
7558
+ if (isVmSpecLike(raw.snapshot)) {
7559
+ cloned.snapshot = cloneVmSpec(raw.snapshot);
7560
+ } else if (raw.snapshot !== void 0) {
7561
+ cloned.snapshot = cloneValue(raw.snapshot);
7562
+ }
7563
+ return cloned;
7564
+ }
7565
+ function cloneVmTemplateRaw(raw) {
7566
+ const cloned = clonePlainObject(raw);
7567
+ if (isVmTemplateLike(raw.template)) {
7568
+ cloned.template = cloneVmTemplate(raw.template);
7569
+ } else if (raw.template !== void 0) {
7570
+ cloned.template = cloneValue(raw.template);
7571
+ }
7572
+ return cloned;
7573
+ }
7574
+ function clonePlainObject(value) {
7575
+ if (!value || typeof value !== "object") {
7576
+ return value;
7577
+ }
7578
+ const cloned = {};
7579
+ for (const [key, entry] of Object.entries(value)) {
7580
+ cloned[key] = cloneValue(entry);
7581
+ }
7582
+ return cloned;
7583
+ }
7584
+ function cloneValue(value) {
7585
+ if (value instanceof VmBaseImage) {
7586
+ return new VmBaseImage(value.toRaw());
7587
+ }
7588
+ if (isVmSpecLike(value)) {
7589
+ return cloneVmSpec(value);
7590
+ }
7591
+ if (isVmTemplateLike(value)) {
7592
+ return cloneVmTemplate(value);
7593
+ }
7594
+ if (Array.isArray(value)) {
7595
+ return value.map((entry) => cloneValue(entry));
7596
+ }
7597
+ if (value && typeof value === "object") {
7598
+ return clonePlainObject(value);
7599
+ }
7600
+ return value;
7601
+ }
7306
7602
 
7307
7603
  class Freestyle {
7308
7604
  /**