pmcf 1.46.3 → 1.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { writeFile, mkdir, copyFile, glob, chmod } from "node:fs/promises";
4
4
  import { join } from "node:path";
5
- import { Host } from "pmcf";
5
+ import { types } from "pmcf";
6
6
  import { writeLines, sectionLines } from "../src/utils.mjs";
7
7
  import { prepare } from "../src/cmd.mjs";
8
8
 
@@ -10,7 +10,7 @@ const { root, args, options } = await prepare();
10
10
 
11
11
  const hostName = args[0];
12
12
 
13
- const host = await root.load(hostName, { type: Host });
13
+ const host = await root.load(hostName, { type: types.host });
14
14
 
15
15
  await generateNetworkDefs(host, options.output);
16
16
  await generateMachineInfo(host, options.output);
@@ -105,7 +105,7 @@ async function generateNetworkDefs(host, dir) {
105
105
  })
106
106
  );
107
107
 
108
- if (ni.arpbridge) {
108
+ if (ni.arpbridge.length) {
109
109
  networkSections.push(
110
110
  "",
111
111
  sectionLines("Link", { Promiscuous: "yes" })
@@ -2,13 +2,13 @@
2
2
 
3
3
  import { mkdir, copyFile } from "node:fs/promises";
4
4
  import { join } from "node:path";
5
- import { Location } from "pmcf";
5
+ import { types } from "pmcf";
6
6
  import { writeLines, sectionLines } from "../src/utils.mjs";
7
7
  import { prepare } from "../src/cmd.mjs";
8
8
 
9
9
  const { root, args, options } = await prepare();
10
10
 
11
- const location = await root.load(args[0], { type: Location });
11
+ const location = await root.load(args[0], { type: types.location });
12
12
 
13
13
  await generateLocationDefs(location, options.output);
14
14
 
@@ -52,7 +52,7 @@ async function generateNamedDefs(owner, targetDir) {
52
52
  };
53
53
  };
54
54
 
55
- for await (const mail of owner.services({ type: "smtp" })) {
55
+ for await (const mail of owner.findServices({ type: "smtp" })) {
56
56
  records.add(
57
57
  createRecord("@", "MX", mail.priority, fullName(mail.owner.domainName))
58
58
  );
@@ -112,7 +112,7 @@ async function generateNamedDefs(owner, targetDir) {
112
112
 
113
113
  if (!hosts.has(host)) {
114
114
  hosts.add(host);
115
- for (const service of host.services()) {
115
+ for (const service of host.findServices()) {
116
116
  if (service.master && service.alias) {
117
117
  zone.records.add(
118
118
  createRecord(service.alias, "CNAME", fullName(host.domainName))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmcf",
3
- "version": "1.46.3",
3
+ "version": "1.47.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -43,11 +43,11 @@
43
43
  "pacc": "^3.3.0"
44
44
  },
45
45
  "devDependencies": {
46
- "@types/node": "^22.13.4",
46
+ "@types/node": "^22.13.5",
47
47
  "ava": "^6.2.0",
48
48
  "c8": "^10.1.3",
49
49
  "documentation": "^14.0.3",
50
- "semantic-release": "^24.2.2",
50
+ "semantic-release": "^24.2.3",
51
51
  "typescript": "^5.7.3"
52
52
  },
53
53
  "engines": {
package/src/base.mjs CHANGED
@@ -1,32 +1,39 @@
1
1
  import { join } from "node:path";
2
2
  import { getAttribute } from "pacc";
3
- import { typesByName } from "./types.mjs";
4
- import { asArray } from "./utils.mjs";
3
+ import { addType, primitives } from "./types.mjs";
4
+
5
+ const BaseTypeDefinition = {
6
+ name: "base",
7
+ owners: [],
8
+ properties: {
9
+ owner: { type: "base", collection: false, writeable: false },
10
+ type: { type: "string", collection: false, writeable: false },
11
+ name: {
12
+ type: "string",
13
+ collection: false,
14
+ identifier: true,
15
+ writeable: true
16
+ },
17
+ description: { type: "string", collection: false, writeable: true },
18
+ directory: { type: "string", collection: false, writeable: false }
19
+ }
20
+ };
5
21
 
6
22
  export class Base {
7
23
  owner;
8
- name;
9
24
  description;
25
+ name;
26
+
27
+ static {
28
+ addType(this);
29
+ }
10
30
 
11
31
  static get typeName() {
12
32
  return this.typeDefinition.name;
13
33
  }
14
34
 
15
35
  static get typeDefinition() {
16
- return {
17
- name: "base",
18
- properties: {
19
- type: { type: "string", writeable: false },
20
- name: { type: "string" },
21
- description: { type: "string" },
22
- directory: { type: "string", writeable: false },
23
- owner: {}
24
- }
25
- };
26
- }
27
-
28
- static get nameLookupName() {
29
- return this.typeName + "Named";
36
+ return BaseTypeDefinition;
30
37
  }
31
38
 
32
39
  static get typeFileName() {
@@ -37,16 +44,6 @@ export class Base {
37
44
  return "**/" + this.typeFileName;
38
45
  }
39
46
 
40
- static async prepareData(root, data) {
41
- return this;
42
- }
43
-
44
- static normalizeName(name) {
45
- if (name !== undefined) {
46
- return name.replace(/\/\w+\.json$/, "");
47
- }
48
- }
49
-
50
47
  constructor(owner, data) {
51
48
  this.owner = owner;
52
49
 
@@ -54,62 +51,151 @@ export class Base {
54
51
  case "string":
55
52
  this.name = data;
56
53
  break;
57
- case "object": {
58
- this.name = data.name;
59
- if (data.description) {
60
- this.description = data.description;
61
- }
54
+ case "object":
55
+ this.read(data, BaseTypeDefinition);
56
+ }
57
+
58
+ if (this.name === undefined) {
59
+ this.error("Missing name", this.owner?.toString(), data);
60
+ }
61
+ }
62
+
63
+ ownerFor(property, data) {
64
+ for (const type of property.type.owners) {
65
+ if (this.typeName === type?.name) {
66
+ return this;
67
+ }
68
+ }
69
+ for (const type of property.type.owners) {
70
+ const owner = this[type?.name];
71
+ if (owner) {
72
+ return owner;
62
73
  }
63
74
  }
75
+
76
+ return this;
64
77
  }
65
78
 
66
- read(data) {
67
- for (const [slotName, typeDef] of Object.entries(
68
- this.constructor.typeDefinition.properties
69
- )) {
70
- let slot = data[slotName];
71
- if (slot) {
72
- delete data[slotName];
73
-
74
- const type =
75
- typeof typeDef.type === "string"
76
- ? typesByName[typeDef.type]
77
- : typeDef.type;
78
-
79
- if (typeDef.collection) {
80
- if (Array.isArray(slot) || typeof slot === "string") {
81
- slot = asArray(slot);
82
- if (type) {
83
- for (const item of slot) {
84
- new type(this, item);
79
+ read(data, type) {
80
+ const assign = (property, value) => {
81
+ if (value !== undefined) {
82
+ if (property.collection) {
83
+ const current = this[property.name];
84
+
85
+ switch (typeof current) {
86
+ case "undefined":
87
+ this[property.name] = value;
88
+ break;
89
+ case "object":
90
+ if (Array.isArray(current)) {
91
+ current.push(value);
92
+ } else {
93
+ if (current instanceof Map || current instanceof Set) {
94
+ // TODO
95
+ this[property.name] = value;
96
+ } else {
97
+ this.error("Unknown collection type", property.name, current);
98
+ }
99
+ }
100
+ break;
101
+ case "function":
102
+ if (value instanceof Base) {
103
+ this.addObject(value);
104
+ } else {
105
+ this.error("Unknown collection type", property.name, current);
85
106
  }
107
+ break;
108
+ }
109
+ } else {
110
+ this[property.name] = value;
111
+ }
112
+ }
113
+ };
114
+
115
+ const instantiateAndAssign = (property, value) => {
116
+ if (primitives.has(property.type)) {
117
+ assign(property, value);
118
+ return;
119
+ }
120
+
121
+ switch (typeof value) {
122
+ case "undefined":
123
+ return;
124
+ case "function":
125
+ this.error("Invalid value", property.name, value);
126
+ break;
127
+
128
+ case "boolean":
129
+ case "bigint":
130
+ case "number":
131
+ case "string":
132
+ {
133
+ value = this.expand(value);
134
+ const object = this.typeNamed(property.type.name, value);
135
+
136
+ if (object) {
137
+ assign(property, object);
86
138
  } else {
87
- this[slotName] = slot;
139
+ if (property.type.constructWithIdentifierOnly) {
140
+ new property.type.clazz(this.ownerFor(property, value), value);
141
+ } else {
142
+ this.finalize(() => {
143
+ value = this.expand(value);
144
+ const object =
145
+ this.typeNamed(property.type.name, value) ||
146
+ this.owner.typeNamed(property.type.name, value) ||
147
+ this.root.typeNamed(property.type.name, value); // TODO
148
+
149
+ assign(property, object);
150
+ });
151
+ }
88
152
  }
153
+ }
154
+ break;
155
+ case "object":
156
+ if (value instanceof property.type.clazz) {
157
+ assign(property, value);
89
158
  } else {
90
- for (const [objectName, objectData] of Object.entries(slot)) {
91
- objectData.name = objectName;
92
- new type(this, objectData);
93
- }
159
+ assign(
160
+ property,
161
+ new property.type.clazz(this.ownerFor(property, value), value)
162
+ );
94
163
  }
95
- } else {
96
- switch (typeDef.type) {
97
- case "undefined":
98
- break;
99
- case "boolean":
100
- case "string":
101
- case "number":
102
- this[slotName] = slot;
103
- break;
164
+ break;
165
+ }
166
+ };
104
167
 
105
- default:
106
- this[slotName] = new type(this, slot);
168
+ for (const property of Object.values(type.properties)) {
169
+ if (property.writeable) {
170
+ const value = data[property.name];
171
+ if (property.collection) {
172
+ if (typeof value === "object") {
173
+ if (Array.isArray(value)) {
174
+ for (const v of value) {
175
+ instantiateAndAssign(property, v);
176
+ }
177
+ } else {
178
+ for (const [objectName, objectData] of Object.entries(value)) {
179
+ objectData[type.identifier.name] = objectName;
180
+ instantiateAndAssign(property, objectData);
181
+ }
182
+ }
183
+ continue;
107
184
  }
108
185
  }
186
+ instantiateAndAssign(property, value);
109
187
  }
110
188
  }
111
189
  }
112
190
 
191
+ typeNamed(typeName, name) {
192
+ return this.owner.typeNamed(typeName, name);
193
+ }
194
+
195
+ addObject(object) {
196
+ return this.owner.addObject(object);
197
+ }
198
+
113
199
  forOwner(owner) {
114
200
  if (this.owner !== owner) {
115
201
  // @ts-ignore
@@ -119,6 +205,16 @@ export class Base {
119
205
  return this;
120
206
  }
121
207
 
208
+ isNamed(name) {
209
+ return name[0] === "/" ? this.fullName === name : this.name === name;
210
+ }
211
+
212
+ relativeName(name) {
213
+ return name?.[0] === "/"
214
+ ? name.substring(this.owner.fullName.length + 1)
215
+ : name;
216
+ }
217
+
122
218
  get typeName() {
123
219
  // @ts-ignore
124
220
  return this.constructor.typeDefinition.name;
@@ -150,16 +246,13 @@ export class Base {
150
246
  }
151
247
 
152
248
  get directory() {
153
- return (
154
- this.#directory ||
155
- (this.owner ? join(this.owner.directory, this.name) : this.name)
156
- );
249
+ return this.#directory || join(this.owner.directory, this.name);
157
250
  }
158
251
 
159
252
  get fullName() {
160
- return this.owner?.fullName && this.name
161
- ? join(this.owner.fullName, this.name)
162
- : this.name;
253
+ return this.name
254
+ ? join(this.owner.fullName, "/", this.name)
255
+ : this.owner.fullName;
163
256
  }
164
257
 
165
258
  expand(object) {
@@ -246,7 +339,29 @@ export class Base {
246
339
  }
247
340
  }
248
341
 
249
- export function extractFrom(object, typeDefinition) {
342
+ export function extractFrom(
343
+ object,
344
+ typeDefinition = object?.constructor?.typeDefinition
345
+ ) {
346
+ if (Array.isArray(object)) {
347
+ if (object.length === 0) {
348
+ return undefined;
349
+ }
350
+
351
+ if (typeDefinition?.identifier) {
352
+ return Object.fromEntries(
353
+ object.map(o => {
354
+ o = extractFrom(o);
355
+ const name = o[typeDefinition.identifier.name];
356
+ delete o[typeDefinition.identifier.name];
357
+ return [name, o];
358
+ })
359
+ );
360
+ }
361
+
362
+ return object.map(o => extractFrom(o));
363
+ }
364
+
250
365
  if (!typeDefinition || object === undefined) {
251
366
  return object;
252
367
  }
@@ -262,19 +377,13 @@ export function extractFrom(object, typeDefinition) {
262
377
  {
263
378
  value = object[name]();
264
379
 
265
- if (Array.isArray(value)) {
266
- if (value.length > 0) {
267
- json[name] = value;
268
- }
269
- } else {
270
- if (typeof value?.next === "function") {
271
- value = [...value];
272
- if (value.length > 0) {
273
- json[name] = value;
274
- }
275
- } else {
276
- json[name] = value;
277
- }
380
+ if (typeof value?.next === "function") {
381
+ value = [...value];
382
+ }
383
+
384
+ value = extractFrom(value, def.type);
385
+ if (value !== undefined) {
386
+ json[name] = value;
278
387
  }
279
388
  }
280
389
  break;
@@ -286,14 +395,17 @@ export function extractFrom(object, typeDefinition) {
286
395
  }
287
396
  } else {
288
397
  if (Array.isArray(value)) {
289
- json[name] = value;
398
+ json[name] = extractFrom(value);
290
399
  } else {
291
- json[name] = Object.fromEntries(
400
+ const resultObject = Object.fromEntries(
292
401
  Object.entries(value).map(([k, v]) => [
293
402
  k,
294
- extractFrom(v, typesByName[def.type])
403
+ v // extractFrom(v, def.type)
295
404
  ])
296
405
  );
406
+ if (Object.keys(resultObject).length > 0) {
407
+ json[name] = resultObject;
408
+ }
297
409
  }
298
410
  }
299
411
  break;
@@ -304,7 +416,7 @@ export function extractFrom(object, typeDefinition) {
304
416
  json[name] = value;
305
417
  }
306
418
  }
307
- typeDefinition = typeDefinition?.extends?.typeDefinition;
419
+ typeDefinition = typeDefinition?.extends;
308
420
  } while (typeDefinition);
309
421
 
310
422
  return json;
package/src/cluster.mjs CHANGED
@@ -1,21 +1,21 @@
1
1
  import { Owner } from "./owner.mjs";
2
2
  import { addType } from "./types.mjs";
3
3
 
4
+ const typeDefinition = {
5
+ name: "cluster",
6
+ owners: [Owner.typeDefinition, "network", "root"],
7
+ priority: 0.7,
8
+ extends: Owner.typeDefinition,
9
+ properties: {}
10
+ };
11
+
4
12
  export class Cluster extends Owner {
5
13
  static {
6
14
  addType(this);
7
15
  }
8
16
 
9
17
  static get typeDefinition() {
10
- return {
11
- name: "cluster",
12
- extends: Owner,
13
- properties: {}
14
- };
18
+ return typeDefinition;
15
19
  }
16
20
 
17
- constructor(owner, data) {
18
- super(owner, data);
19
- owner.addObject(this);
20
- }
21
21
  }
package/src/dns.mjs CHANGED
@@ -1,47 +1,59 @@
1
1
  import { Base } from "./base.mjs";
2
- import { asArray } from "./utils.mjs";
3
2
  import { addType } from "./types.mjs";
4
3
 
4
+ const DNSServiceTypeDefinition = {
5
+ name: "dns",
6
+ owners: ["location", "owner", "network", "cluster", "root"],
7
+ priority: 0.1,
8
+ properties: {
9
+ hasSVRRecords: { type: "boolean", collection: false, writeable: true },
10
+ hasCatalog: { type: "boolean", collection: false, writeable: true },
11
+ recordTTL: { type: "string", collection: false, writeable: true },
12
+ soaUpdates: { type: "number", collection: true, writeable: true },
13
+ forwardsTo: { type: "network", collection: true, writeable: true },
14
+ allowedUpdates: { type: "string", collection: true, writeable: true }
15
+ }
16
+ };
17
+
5
18
  export class DNSService extends Base {
6
19
  allowedUpdates = [];
7
20
  recordTTL = "1W";
8
21
  soaUpdates = [36000, 72000, 600000, 60000];
9
22
  hasSVRRecords = true;
10
23
  hasCatalog = true;
11
- forwardsTo = [];
24
+ #forwardsTo = [];
12
25
 
13
26
  static {
14
27
  addType(this);
15
28
  }
16
29
 
17
30
  static get typeDefinition() {
18
- return {
19
- name: "dns",
20
- properties: {
21
- hasSVRRecords: { type: "boolean" },
22
- hasCatalog: { type: "boolean" },
23
- recordTTL: { type: "string" },
24
- soaUpdates: { type: "number", collection: true },
25
- forwardsTo: { type: "host", collection: true },
26
- allowedUpdates: { type: "string", collection: true }
27
- }
28
- };
31
+ return DNSServiceTypeDefinition;
29
32
  }
30
33
 
31
34
  constructor(owner, data) {
35
+ if (!data.name) {
36
+ data.name = DNSServiceTypeDefinition.name; // TODO
37
+ }
32
38
  super(owner, data);
33
- Object.assign(this, data);
34
- owner.addObject(this);
39
+ this.read(data, DNSServiceTypeDefinition);
40
+ }
41
+
42
+ set forwardsTo(value) {
43
+ this.#forwardsTo.push(value);
44
+ }
45
+
46
+ get forwardsTo() {
47
+ return this.#forwardsTo;
35
48
  }
36
49
 
37
- async *services() {
38
- const filter = { type: "dns" };
50
+ async *findServices() {
51
+ const filter = { type: DNSServiceTypeDefinition.name };
39
52
 
40
- yield* this.owner.services(filter);
53
+ yield* this.owner.findServices(filter);
41
54
 
42
- for (const s of asArray(this.forwardsTo)) {
43
- const owner = await this.owner.root.load(s);
44
- yield* owner.services(filter);
55
+ for (const s of this.forwardsTo) {
56
+ yield* s.findServices(filter);
45
57
  }
46
58
  }
47
59
 
@@ -50,7 +62,7 @@ export class DNSService extends Base {
50
62
  }
51
63
 
52
64
  async resolvedConfig() {
53
- const dnsServices = (await Array.fromAsync(this.services())).sort(
65
+ const dnsServices = (await Array.fromAsync(this.findServices())).sort(
54
66
  (a, b) => a.priority - b.priority
55
67
  );
56
68