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.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() {
@@ -6477,9 +6598,74 @@ class VmSpec {
6477
6598
  return this;
6478
6599
  }
6479
6600
  runCommands(...commands) {
6480
- const image = normalizeBaseImage(this.raw.baseImage) ?? new VmBaseImage();
6481
- image.runCommands(...commands);
6482
- 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
+ };
6483
6669
  return this;
6484
6670
  }
6485
6671
  repo(repo, path) {
@@ -6506,9 +6692,76 @@ class VmSpec {
6506
6692
  function isVmSpecLike$1(value) {
6507
6693
  return !!value && typeof value === "object" && "raw" in value && "with" in value;
6508
6694
  }
6509
- function isVmTemplateLike(value) {
6695
+ function isVmTemplateLike$1(value) {
6510
6696
  return !!value && typeof value === "object" && "raw" in value && "with" in value;
6511
6697
  }
6698
+ function cloneVmSpecTree(spec) {
6699
+ const clonedRaw = cloneVmSpecRaw$1(spec.raw);
6700
+ return new VmSpec({
6701
+ ...clonedRaw,
6702
+ with: { ...spec.builders },
6703
+ __withDiscriminators: spec.getBuilderDiscriminators()
6704
+ });
6705
+ }
6706
+ function isVmSpecInstance(value) {
6707
+ return value instanceof VmSpec;
6708
+ }
6709
+ function isVmTemplateInstance(value) {
6710
+ return value instanceof VmTemplate;
6711
+ }
6712
+ function cloneVmTemplateTree(template) {
6713
+ const clonedRaw = cloneVmTemplateRaw$1(template.raw);
6714
+ return new VmTemplate({
6715
+ ...clonedRaw,
6716
+ with: { ...template.with }
6717
+ });
6718
+ }
6719
+ function cloneVmSpecRaw$1(raw) {
6720
+ const cloned = cloneVmTemplateRaw$1(raw);
6721
+ if (isVmSpecInstance(raw.snapshot)) {
6722
+ cloned.snapshot = cloneVmSpecTree(raw.snapshot);
6723
+ } else if (raw.snapshot !== void 0) {
6724
+ cloned.snapshot = cloneVmValue(raw.snapshot);
6725
+ }
6726
+ return cloned;
6727
+ }
6728
+ function cloneVmTemplateRaw$1(raw) {
6729
+ const cloned = cloneVmPlainObject(raw);
6730
+ if (isVmTemplateInstance(raw.template)) {
6731
+ cloned.template = cloneVmTemplateTree(raw.template);
6732
+ } else if (raw.template !== void 0) {
6733
+ cloned.template = cloneVmValue(raw.template);
6734
+ }
6735
+ return cloned;
6736
+ }
6737
+ function cloneVmPlainObject(value) {
6738
+ if (!value || typeof value !== "object") {
6739
+ return value;
6740
+ }
6741
+ const cloned = {};
6742
+ for (const [key, entry] of Object.entries(value)) {
6743
+ cloned[key] = cloneVmValue(entry);
6744
+ }
6745
+ return cloned;
6746
+ }
6747
+ function cloneVmValue(value) {
6748
+ if (value instanceof VmBaseImage) {
6749
+ return new VmBaseImage(value.toRaw());
6750
+ }
6751
+ if (isVmSpecInstance(value)) {
6752
+ return cloneVmSpecTree(value);
6753
+ }
6754
+ if (isVmTemplateInstance(value)) {
6755
+ return cloneVmTemplateTree(value);
6756
+ }
6757
+ if (Array.isArray(value)) {
6758
+ return value.map((entry) => cloneVmValue(entry));
6759
+ }
6760
+ if (value && typeof value === "object") {
6761
+ return cloneVmPlainObject(value);
6762
+ }
6763
+ return value;
6764
+ }
6512
6765
  async function convertSpecSnapshotsToTemplates(spec, processBuilders = true) {
6513
6766
  if (!isVmSpecLike$1(spec.raw.snapshot)) {
6514
6767
  return void 0;
@@ -6620,7 +6873,17 @@ class VmsNamespace {
6620
6873
  snapshots;
6621
6874
  async create(options = {}) {
6622
6875
  if (isVmSpecLike$1(options)) {
6623
- options = { spec: options };
6876
+ options = { spec: cloneVmSpecTree(options) };
6877
+ } else {
6878
+ if (isVmSpecLike$1(options.spec)) {
6879
+ options.spec = cloneVmSpecTree(options.spec);
6880
+ }
6881
+ if (isVmSpecLike$1(options.snapshot)) {
6882
+ options.snapshot = cloneVmSpecTree(options.snapshot);
6883
+ }
6884
+ if (isVmTemplateLike$1(options.template)) {
6885
+ options.template = cloneVmTemplateTree(options.template);
6886
+ }
6624
6887
  }
6625
6888
  if (isVmSpecLike$1(options.snapshot)) {
6626
6889
  if (isVmSpecLike$1(options.spec)) {
@@ -6632,21 +6895,8 @@ class VmsNamespace {
6632
6895
  }
6633
6896
  }
6634
6897
  if (options.systemd?.services) {
6635
- const normalizedServices = options.systemd.services.map(
6636
- (service) => {
6637
- return {
6638
- ...service,
6639
- after: service.after?.map(
6640
- (s) => s.includes(".") ? s : `${s}.service`
6641
- ),
6642
- requires: service.requires?.map(
6643
- (s) => s.includes(".") ? s : `${s}.service`
6644
- ),
6645
- wantedBy: service.wantedBy?.map(
6646
- (s) => s.includes(".") ? s : `${s}.service`
6647
- )
6648
- };
6649
- }
6898
+ const normalizedServices = normalizeSystemdServices(
6899
+ options.systemd.services
6650
6900
  );
6651
6901
  const { services: processedServices, additionalFiles: bashFiles } = processSystemdServices(
6652
6902
  normalizedServices,
@@ -6656,19 +6906,8 @@ class VmsNamespace {
6656
6906
  options.additionalFiles = bashFiles;
6657
6907
  }
6658
6908
  if (options.systemd?.patchedServices) {
6659
- options.systemd.patchedServices = options.systemd.patchedServices.map(
6660
- (service) => {
6661
- service.after = service.after?.map(
6662
- (s) => s.includes(".") ? s : `${s}.service`
6663
- );
6664
- service.requires = service.requires?.map(
6665
- (s) => s.includes(".") ? s : `${s}.service`
6666
- );
6667
- service.wantedBy = service.wantedBy?.map(
6668
- (s) => s.includes(".") ? s : `${s}.service`
6669
- );
6670
- return service;
6671
- }
6909
+ options.systemd.patchedServices = normalizePatchedServices(
6910
+ options.systemd.patchedServices
6672
6911
  );
6673
6912
  }
6674
6913
  if ("snapshot" in options) {
@@ -6701,7 +6940,7 @@ class VmsNamespace {
6701
6940
  const specBuilders = isVmSpecLike$1(
6702
6941
  options.spec
6703
6942
  ) ? collectSpecBuilders(options.spec) : void 0;
6704
- const templateBuilders = isVmTemplateLike(options.template) ? options.template.with : void 0;
6943
+ const templateBuilders = isVmTemplateLike$1(options.template) ? options.template.with : void 0;
6705
6944
  const builders = {
6706
6945
  ...templateBuilders || {},
6707
6946
  ...specBuilders || {},
@@ -6709,10 +6948,10 @@ class VmsNamespace {
6709
6948
  };
6710
6949
  const { with: _, spec: _spec, ...baseConfig } = options;
6711
6950
  let config = baseConfig;
6712
- if (isVmTemplateLike(config.template)) {
6951
+ if (isVmTemplateLike$1(config.template)) {
6713
6952
  config.template = await processTemplateTree(config.template);
6714
6953
  }
6715
- if (isVmTemplateLike(config.template)) {
6954
+ if (isVmTemplateLike$1(config.template)) {
6716
6955
  config.template = await ensureNestedTemplates(
6717
6956
  config.template,
6718
6957
  this.snapshots
@@ -6726,18 +6965,9 @@ class VmsNamespace {
6726
6965
  }
6727
6966
  }
6728
6967
  if (config.systemd?.services) {
6729
- const normalizedServices = config.systemd.services.map((service) => ({
6730
- ...service,
6731
- after: service.after?.map(
6732
- (s) => s.includes(".") ? s : `${s}.service`
6733
- ),
6734
- requires: service.requires?.map(
6735
- (s) => s.includes(".") ? s : `${s}.service`
6736
- ),
6737
- wantedBy: service.wantedBy?.map(
6738
- (s) => s.includes(".") ? s : `${s}.service`
6739
- )
6740
- }));
6968
+ const normalizedServices = normalizeSystemdServices(
6969
+ config.systemd.services
6970
+ );
6741
6971
  const { services: processedServices, additionalFiles: bashFiles } = processSystemdServices(
6742
6972
  normalizedServices,
6743
6973
  config.additionalFiles ?? {}
@@ -6746,28 +6976,20 @@ class VmsNamespace {
6746
6976
  config.additionalFiles = bashFiles;
6747
6977
  }
6748
6978
  if (config.systemd?.patchedServices) {
6749
- config.systemd.patchedServices = config.systemd.patchedServices.map(
6750
- (service) => {
6751
- service.after = service.after?.map(
6752
- (s) => s.includes(".") ? s : `${s}.service`
6753
- );
6754
- service.requires = service.requires?.map(
6755
- (s) => s.includes(".") ? s : `${s}.service`
6756
- );
6757
- service.wantedBy = service.wantedBy?.map(
6758
- (s) => s.includes(".") ? s : `${s}.service`
6759
- );
6760
- return service;
6761
- }
6979
+ config.systemd.patchedServices = normalizePatchedServices(
6980
+ config.systemd.patchedServices
6762
6981
  );
6763
6982
  }
6764
6983
  config.git = normalizeGitOptions(config.git);
6765
6984
  const serializedBaseImage = normalizeBaseImage(config.baseImage)?.toRaw();
6766
- const rawTemplate = isVmTemplateLike(config.template) ? config.template.raw : config.template;
6985
+ const rawTemplate = isVmTemplateLike$1(config.template) ? config.template.raw : config.template;
6767
6986
  const requestTemplate = serializedBaseImage ? {
6768
6987
  ...rawTemplate ?? {},
6769
6988
  baseImage: serializedBaseImage
6770
6989
  } : rawTemplate;
6990
+ const normalizedRequestTemplate = requestTemplate ? normalizeTemplateForRequest(
6991
+ requestTemplate
6992
+ ) : requestTemplate;
6771
6993
  const {
6772
6994
  baseImage: _baseImage,
6773
6995
  template: _template,
@@ -6776,7 +6998,7 @@ class VmsNamespace {
6776
6998
  const response = await this.freestyle._apiClient.post("/v1/vms", {
6777
6999
  body: {
6778
7000
  ...requestConfig,
6779
- template: requestTemplate,
7001
+ template: normalizedRequestTemplate,
6780
7002
  // Cast systemd since we've processed SystemdServiceInput[] to RawSystemdService[]
6781
7003
  systemd: config.systemd,
6782
7004
  // Normalize git options - default config to {}
@@ -6871,6 +7093,35 @@ function enhanceError(e) {
6871
7093
  if (!e.message.includes("create --snapshot")) {
6872
7094
  e.message = `${e.message}. Hint: use \`npx freestyle-sandboxes@latest vm create --snapshot ${e.body.snapshotId} --ssh --delete\` to debug.`;
6873
7095
  }
7096
+ if (e.body.diagnostics) {
7097
+ const d = e.body.diagnostics;
7098
+ const parts = [];
7099
+ if (d.serviceStates.length > 0) {
7100
+ parts.push("Service states:");
7101
+ for (const g of d.serviceStates) {
7102
+ parts.push(
7103
+ ` ${g.activeState}(${g.subState}): ${g.services.join(", ")}`
7104
+ );
7105
+ }
7106
+ }
7107
+ const allLogs = [
7108
+ ...d.failedServiceLogs ?? [],
7109
+ ...d.additionalFailedServiceLogs ?? []
7110
+ ];
7111
+ if (allLogs.length > 0) {
7112
+ parts.push("Failed service logs:");
7113
+ for (const l of allLogs) {
7114
+ parts.push(` --- ${l.unitName} ---`);
7115
+ parts.push(` ${l.log}`);
7116
+ }
7117
+ }
7118
+ if (parts.length > 0) {
7119
+ e.message = `${e.message}
7120
+
7121
+ Diagnostics:
7122
+ ${parts.join("\n")}`;
7123
+ }
7124
+ }
6874
7125
  }
6875
7126
  return e;
6876
7127
  }
@@ -6909,7 +7160,12 @@ class VmSnapshotsNamespace {
6909
7160
  this.apiClient = apiClient;
6910
7161
  }
6911
7162
  async ensure(options) {
6912
- let requestOptions = options;
7163
+ let requestOptions = {
7164
+ ...options,
7165
+ spec: isVmSpecLike$1(options.spec) ? cloneVmSpecTree(options.spec) : void 0,
7166
+ snapshot: isVmSpecLike$1(options.snapshot) ? cloneVmSpecTree(options.snapshot) : void 0,
7167
+ template: isVmTemplateLike$1(options.template) ? cloneVmTemplateTree(options.template) : options.template
7168
+ };
6913
7169
  if (isVmSpecLike$1(requestOptions.snapshot)) {
6914
7170
  if (isVmSpecLike$1(requestOptions.spec)) {
6915
7171
  if (!requestOptions.spec.raw.snapshot) {
@@ -6938,12 +7194,12 @@ class VmSnapshotsNamespace {
6938
7194
  const { spec: _spec, ...rest } = requestOptions;
6939
7195
  requestOptions = rest;
6940
7196
  }
6941
- if (isVmTemplateLike(requestOptions.template)) {
7197
+ if (isVmTemplateLike$1(requestOptions.template)) {
6942
7198
  const processedTemplate = await processTemplateTree(
6943
7199
  requestOptions.template
6944
7200
  );
6945
7201
  requestOptions.template = processedTemplate;
6946
- if (isVmTemplateLike(processedTemplate.raw.template)) {
7202
+ if (isVmTemplateLike$1(processedTemplate.raw.template)) {
6947
7203
  requestOptions.template = await ensureNestedTemplates(
6948
7204
  processedTemplate,
6949
7205
  this
@@ -6958,7 +7214,9 @@ class VmSnapshotsNamespace {
6958
7214
  return this.apiClient.post("/v1/vms/snapshots", {
6959
7215
  body: {
6960
7216
  ...requestOptions,
6961
- template: isVmTemplateLike(requestOptions.template) ? requestOptions.template.raw : requestOptions.template
7217
+ template: normalizeTemplateForRequest(
7218
+ isVmTemplateLike$1(requestOptions.template) ? requestOptions.template.raw : requestOptions.template
7219
+ )
6962
7220
  }
6963
7221
  }).catch((e) => {
6964
7222
  enhanceError(e);
@@ -6978,22 +7236,9 @@ async function processTemplateTree(template) {
6978
7236
  );
6979
7237
  }
6980
7238
  if (template.raw.systemd?.services) {
6981
- const normalizedServices = template.raw.systemd.services.map((service) => ({
6982
- ...service,
6983
- after: service.after?.map((s) => s.includes(".") ? s : `${s}.service`),
6984
- requires: service.requires?.map(
6985
- (s) => s.includes(".") ? s : `${s}.service`
6986
- ),
6987
- wantedBy: service.wantedBy?.map(
6988
- (s) => s.includes(".") ? s : `${s}.service`
6989
- )
6990
- }));
6991
- const { services: processedServices, additionalFiles: bashFiles } = processSystemdServices(
6992
- normalizedServices,
6993
- template.raw.additionalFiles ?? {}
7239
+ template.raw.systemd.services = normalizeSystemdServices(
7240
+ template.raw.systemd.services
6994
7241
  );
6995
- template.raw.systemd.services = processedServices;
6996
- template.raw.additionalFiles = bashFiles;
6997
7242
  }
6998
7243
  for (const key in template.with) {
6999
7244
  const builder = template.with[key];
@@ -7010,7 +7255,7 @@ async function processTemplateTree(template) {
7010
7255
  }
7011
7256
  if (builder.configureNestedTemplate) {
7012
7257
  let nestedTemplate;
7013
- if (isVmTemplateLike(template.raw.template)) {
7258
+ if (isVmTemplateLike$1(template.raw.template)) {
7014
7259
  nestedTemplate = template.raw.template;
7015
7260
  } else {
7016
7261
  nestedTemplate = new VmTemplate({});
@@ -7033,38 +7278,16 @@ async function processTemplateTree(template) {
7033
7278
  );
7034
7279
  }
7035
7280
  if (template.raw.systemd?.services) {
7036
- const normalizedServices = template.raw.systemd.services.map((service) => ({
7037
- ...service,
7038
- after: service.after?.map((s) => s.includes(".") ? s : `${s}.service`),
7039
- requires: service.requires?.map(
7040
- (s) => s.includes(".") ? s : `${s}.service`
7041
- ),
7042
- wantedBy: service.wantedBy?.map(
7043
- (s) => s.includes(".") ? s : `${s}.service`
7044
- )
7045
- }));
7046
- const { services: processedServices, additionalFiles: bashFiles } = processSystemdServices(
7047
- normalizedServices,
7048
- template.raw.additionalFiles ?? {}
7281
+ template.raw.systemd.services = normalizeSystemdServices(
7282
+ template.raw.systemd.services
7049
7283
  );
7050
- template.raw.systemd.services = processedServices;
7051
- template.raw.additionalFiles = bashFiles;
7052
7284
  }
7053
7285
  if (template.raw.systemd?.patchedServices) {
7054
- template.raw.systemd.patchedServices = template.raw.systemd.patchedServices.map((service) => ({
7055
- ...service,
7056
- after: service.after?.map(
7057
- (s) => s.includes(".") ? s : `${s}.service`
7058
- ),
7059
- requires: service.requires?.map(
7060
- (s) => s.includes(".") ? s : `${s}.service`
7061
- ),
7062
- wantedBy: service.wantedBy?.map(
7063
- (s) => s.includes(".") ? s : `${s}.service`
7064
- )
7065
- }));
7286
+ template.raw.systemd.patchedServices = normalizePatchedServices(
7287
+ template.raw.systemd.patchedServices
7288
+ );
7066
7289
  }
7067
- if (isVmTemplateLike(template.raw.template)) {
7290
+ if (isVmTemplateLike$1(template.raw.template)) {
7068
7291
  template.raw.template = await processTemplateTree(
7069
7292
  template.raw.template
7070
7293
  );
@@ -7073,7 +7296,7 @@ async function processTemplateTree(template) {
7073
7296
  }
7074
7297
  async function ensureNestedTemplates(template, snapshots) {
7075
7298
  const templates = [template];
7076
- while (isVmTemplateLike(templates.at(-1)?.raw.template)) {
7299
+ while (isVmTemplateLike$1(templates.at(-1)?.raw.template)) {
7077
7300
  const innerTemplate = templates.at(-1).raw.template;
7078
7301
  templates.at(-1).raw.template = void 0;
7079
7302
  templates.push(innerTemplate);
@@ -7258,7 +7481,7 @@ async function readFiles(directory) {
7258
7481
  }
7259
7482
 
7260
7483
  async function debugCreateRequests(freestyle, optionsOrSpec = {}) {
7261
- const options = isVmSpecLike(optionsOrSpec) ? { spec: optionsOrSpec } : optionsOrSpec;
7484
+ const options = isVmSpecLike(optionsOrSpec) ? { spec: cloneVmSpec(optionsOrSpec) } : cloneCreateOptions(optionsOrSpec);
7262
7485
  const requests = [];
7263
7486
  let snapshotCounter = 0;
7264
7487
  let vmCounter = 0;
@@ -7305,6 +7528,79 @@ async function debugCreateRequests(freestyle, optionsOrSpec = {}) {
7305
7528
  function isVmSpecLike(value) {
7306
7529
  return !!value && typeof value === "object" && "raw" in value && "with" in value;
7307
7530
  }
7531
+ function isVmTemplateLike(value) {
7532
+ return !!value && typeof value === "object" && "raw" in value && "with" in value;
7533
+ }
7534
+ function cloneCreateOptions(options) {
7535
+ return {
7536
+ ...clonePlainObject(options),
7537
+ with: options.with,
7538
+ template: isVmTemplateLike(options.template) ? cloneVmTemplate(options.template) : cloneValue(options.template),
7539
+ spec: isVmSpecLike(options.spec) ? cloneVmSpec(options.spec) : void 0,
7540
+ snapshot: isVmSpecLike(options.snapshot) ? cloneVmSpec(options.snapshot) : void 0
7541
+ };
7542
+ }
7543
+ function cloneVmSpec(spec) {
7544
+ const clonedRaw = cloneVmSpecRaw(spec.raw);
7545
+ return new VmSpec({
7546
+ ...clonedRaw,
7547
+ with: { ...spec.builders },
7548
+ __withDiscriminators: typeof spec.getBuilderDiscriminators === "function" ? spec.getBuilderDiscriminators() : void 0
7549
+ });
7550
+ }
7551
+ function cloneVmTemplate(template) {
7552
+ const clonedRaw = cloneVmTemplateRaw(template.raw);
7553
+ return new VmTemplate({
7554
+ ...clonedRaw,
7555
+ with: { ...template.with }
7556
+ });
7557
+ }
7558
+ function cloneVmSpecRaw(raw) {
7559
+ const cloned = cloneVmTemplateRaw(raw);
7560
+ if (isVmSpecLike(raw.snapshot)) {
7561
+ cloned.snapshot = cloneVmSpec(raw.snapshot);
7562
+ } else if (raw.snapshot !== void 0) {
7563
+ cloned.snapshot = cloneValue(raw.snapshot);
7564
+ }
7565
+ return cloned;
7566
+ }
7567
+ function cloneVmTemplateRaw(raw) {
7568
+ const cloned = clonePlainObject(raw);
7569
+ if (isVmTemplateLike(raw.template)) {
7570
+ cloned.template = cloneVmTemplate(raw.template);
7571
+ } else if (raw.template !== void 0) {
7572
+ cloned.template = cloneValue(raw.template);
7573
+ }
7574
+ return cloned;
7575
+ }
7576
+ function clonePlainObject(value) {
7577
+ if (!value || typeof value !== "object") {
7578
+ return value;
7579
+ }
7580
+ const cloned = {};
7581
+ for (const [key, entry] of Object.entries(value)) {
7582
+ cloned[key] = cloneValue(entry);
7583
+ }
7584
+ return cloned;
7585
+ }
7586
+ function cloneValue(value) {
7587
+ if (value instanceof VmBaseImage) {
7588
+ return new VmBaseImage(value.toRaw());
7589
+ }
7590
+ if (isVmSpecLike(value)) {
7591
+ return cloneVmSpec(value);
7592
+ }
7593
+ if (isVmTemplateLike(value)) {
7594
+ return cloneVmTemplate(value);
7595
+ }
7596
+ if (Array.isArray(value)) {
7597
+ return value.map((entry) => cloneValue(entry));
7598
+ }
7599
+ if (value && typeof value === "object") {
7600
+ return clonePlainObject(value);
7601
+ }
7602
+ return value;
7603
+ }
7308
7604
 
7309
7605
  class Freestyle {
7310
7606
  /**