permachine 0.8.0 → 0.9.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.
Files changed (2) hide show
  1. package/dist/cli.js +130 -89
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -6025,6 +6025,104 @@ function stripJsonComments(jsonString, { whitespace = true, trailingCommas = fal
6025
6025
  return result + buffer + remaining;
6026
6026
  }
6027
6027
 
6028
+ // src/core/errors.ts
6029
+ class PermachineError extends Error {
6030
+ constructor(message) {
6031
+ super(message);
6032
+ this.name = "PermachineError";
6033
+ }
6034
+ }
6035
+
6036
+ class NestedFilteredDirectoryError extends PermachineError {
6037
+ outerDir;
6038
+ innerDir;
6039
+ constructor(outerDir, innerDir) {
6040
+ super(`Nested machine-specific directories are not supported.
6041
+ ` + `Found: ${innerDir} inside ${outerDir}
6042
+ ` + `Only one level of directory filtering is allowed.`);
6043
+ this.name = "NestedFilteredDirectoryError";
6044
+ this.outerDir = outerDir;
6045
+ this.innerDir = innerDir;
6046
+ }
6047
+ }
6048
+
6049
+ class DirectoryConflictError extends PermachineError {
6050
+ outputPath;
6051
+ sources;
6052
+ constructor(outputPath, sources) {
6053
+ super(`Multiple directories would output to the same path: ${outputPath}
6054
+ ` + `Sources: ${sources.join(", ")}
6055
+ ` + `Only one machine-specific directory can match per output path.`);
6056
+ this.name = "DirectoryConflictError";
6057
+ this.outputPath = outputPath;
6058
+ this.sources = sources;
6059
+ }
6060
+ }
6061
+
6062
+ class FileDirectoryConflictError extends PermachineError {
6063
+ outputPath;
6064
+ fileSource;
6065
+ dirSource;
6066
+ constructor(outputPath, fileSource, dirSource) {
6067
+ super(`Both a file merge and directory copy would output to: ${outputPath}
6068
+ ` + `File source: ${fileSource}
6069
+ ` + `Directory source: ${dirSource}
6070
+ ` + `Remove one of these to resolve the conflict.`);
6071
+ this.name = "FileDirectoryConflictError";
6072
+ this.outputPath = outputPath;
6073
+ this.fileSource = fileSource;
6074
+ this.dirSource = dirSource;
6075
+ }
6076
+ }
6077
+
6078
+ class BaseDirectoryNotSupportedError extends PermachineError {
6079
+ dirPath;
6080
+ constructor(dirPath) {
6081
+ super(`Base directories are not supported: ${dirPath}
6082
+ ` + `Unlike files, directories do not support .base fallback.
6083
+ ` + `Use machine-specific directories only (e.g., mydir.{machine=X}/).`);
6084
+ this.name = "BaseDirectoryNotSupportedError";
6085
+ this.dirPath = dirPath;
6086
+ }
6087
+ }
6088
+
6089
+ class DirectoryCopyError extends PermachineError {
6090
+ sourcePath;
6091
+ outputPath;
6092
+ cause;
6093
+ constructor(sourcePath, outputPath, cause) {
6094
+ super(`Failed to copy directory: ${sourcePath} → ${outputPath}
6095
+ ` + (cause ? `Cause: ${cause.message}` : ""));
6096
+ this.name = "DirectoryCopyError";
6097
+ this.sourcePath = sourcePath;
6098
+ this.outputPath = outputPath;
6099
+ this.cause = cause;
6100
+ }
6101
+ }
6102
+
6103
+ class CleanupError extends PermachineError {
6104
+ path;
6105
+ cause;
6106
+ constructor(path2, cause) {
6107
+ super(`Failed to cleanup stale output: ${path2}
6108
+ ` + (cause ? `Cause: ${cause.message}` : ""));
6109
+ this.name = "CleanupError";
6110
+ this.path = path2;
6111
+ this.cause = cause;
6112
+ }
6113
+ }
6114
+
6115
+ class ArrayMergeError extends PermachineError {
6116
+ key;
6117
+ constructor(key) {
6118
+ super(`Cannot merge arrays containing non-primitive values at key "${key}".
6119
+ ` + `Array merging only supports primitive values (string, number, boolean, null).
6120
+ ` + `Arrays containing objects or nested arrays cannot be merged.`);
6121
+ this.name = "ArrayMergeError";
6122
+ this.key = key;
6123
+ }
6124
+ }
6125
+
6028
6126
  // src/adapters/json-adapter.ts
6029
6127
  class JsonAdapter {
6030
6128
  canHandle(extension) {
@@ -6046,7 +6144,10 @@ class JsonAdapter {
6046
6144
  return JSON.stringify(data, null, 2) + `
6047
6145
  `;
6048
6146
  }
6049
- deepMerge(base, machine) {
6147
+ deepMerge(base, machine, path2 = "") {
6148
+ if (Array.isArray(base) && Array.isArray(machine)) {
6149
+ return this.mergeArrays(base, machine, path2);
6150
+ }
6050
6151
  if (machine === null || typeof machine !== "object" || Array.isArray(machine)) {
6051
6152
  return machine;
6052
6153
  }
@@ -6056,8 +6157,9 @@ class JsonAdapter {
6056
6157
  const result = { ...base };
6057
6158
  for (const key in machine) {
6058
6159
  if (Object.prototype.hasOwnProperty.call(machine, key)) {
6160
+ const newPath = path2 ? `${path2}.${key}` : key;
6059
6161
  if (key in result) {
6060
- result[key] = this.deepMerge(result[key], machine[key]);
6162
+ result[key] = this.deepMerge(result[key], machine[key], newPath);
6061
6163
  } else {
6062
6164
  result[key] = machine[key];
6063
6165
  }
@@ -6065,6 +6167,32 @@ class JsonAdapter {
6065
6167
  }
6066
6168
  return result;
6067
6169
  }
6170
+ isPrimitive(value) {
6171
+ return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
6172
+ }
6173
+ isArrayOfPrimitives(arr) {
6174
+ return arr.every((item) => this.isPrimitive(item));
6175
+ }
6176
+ mergeArrays(base, machine, path2) {
6177
+ if (!this.isArrayOfPrimitives(base) || !this.isArrayOfPrimitives(machine)) {
6178
+ throw new ArrayMergeError(path2 || "root");
6179
+ }
6180
+ const seen = new Set;
6181
+ const result = [];
6182
+ for (const item of base) {
6183
+ if (!seen.has(item)) {
6184
+ seen.add(item);
6185
+ result.push(item);
6186
+ }
6187
+ }
6188
+ for (const item of machine) {
6189
+ if (!seen.has(item)) {
6190
+ seen.add(item);
6191
+ result.push(item);
6192
+ }
6193
+ }
6194
+ return result;
6195
+ }
6068
6196
  }
6069
6197
 
6070
6198
  // src/adapters/env-adapter.ts
@@ -6144,93 +6272,6 @@ function getFileType(filename) {
6144
6272
  return "unknown";
6145
6273
  }
6146
6274
 
6147
- // src/core/errors.ts
6148
- class PermachineError extends Error {
6149
- constructor(message) {
6150
- super(message);
6151
- this.name = "PermachineError";
6152
- }
6153
- }
6154
-
6155
- class NestedFilteredDirectoryError extends PermachineError {
6156
- outerDir;
6157
- innerDir;
6158
- constructor(outerDir, innerDir) {
6159
- super(`Nested machine-specific directories are not supported.
6160
- ` + `Found: ${innerDir} inside ${outerDir}
6161
- ` + `Only one level of directory filtering is allowed.`);
6162
- this.name = "NestedFilteredDirectoryError";
6163
- this.outerDir = outerDir;
6164
- this.innerDir = innerDir;
6165
- }
6166
- }
6167
-
6168
- class DirectoryConflictError extends PermachineError {
6169
- outputPath;
6170
- sources;
6171
- constructor(outputPath, sources) {
6172
- super(`Multiple directories would output to the same path: ${outputPath}
6173
- ` + `Sources: ${sources.join(", ")}
6174
- ` + `Only one machine-specific directory can match per output path.`);
6175
- this.name = "DirectoryConflictError";
6176
- this.outputPath = outputPath;
6177
- this.sources = sources;
6178
- }
6179
- }
6180
-
6181
- class FileDirectoryConflictError extends PermachineError {
6182
- outputPath;
6183
- fileSource;
6184
- dirSource;
6185
- constructor(outputPath, fileSource, dirSource) {
6186
- super(`Both a file merge and directory copy would output to: ${outputPath}
6187
- ` + `File source: ${fileSource}
6188
- ` + `Directory source: ${dirSource}
6189
- ` + `Remove one of these to resolve the conflict.`);
6190
- this.name = "FileDirectoryConflictError";
6191
- this.outputPath = outputPath;
6192
- this.fileSource = fileSource;
6193
- this.dirSource = dirSource;
6194
- }
6195
- }
6196
-
6197
- class BaseDirectoryNotSupportedError extends PermachineError {
6198
- dirPath;
6199
- constructor(dirPath) {
6200
- super(`Base directories are not supported: ${dirPath}
6201
- ` + `Unlike files, directories do not support .base fallback.
6202
- ` + `Use machine-specific directories only (e.g., mydir.{machine=X}/).`);
6203
- this.name = "BaseDirectoryNotSupportedError";
6204
- this.dirPath = dirPath;
6205
- }
6206
- }
6207
-
6208
- class DirectoryCopyError extends PermachineError {
6209
- sourcePath;
6210
- outputPath;
6211
- cause;
6212
- constructor(sourcePath, outputPath, cause) {
6213
- super(`Failed to copy directory: ${sourcePath} → ${outputPath}
6214
- ` + (cause ? `Cause: ${cause.message}` : ""));
6215
- this.name = "DirectoryCopyError";
6216
- this.sourcePath = sourcePath;
6217
- this.outputPath = outputPath;
6218
- this.cause = cause;
6219
- }
6220
- }
6221
-
6222
- class CleanupError extends PermachineError {
6223
- path;
6224
- cause;
6225
- constructor(path3, cause) {
6226
- super(`Failed to cleanup stale output: ${path3}
6227
- ` + (cause ? `Cause: ${cause.message}` : ""));
6228
- this.name = "CleanupError";
6229
- this.path = path3;
6230
- this.cause = cause;
6231
- }
6232
- }
6233
-
6234
6275
  // src/core/file-scanner.ts
6235
6276
  async function scanForMergeOperations(machineName, cwd = process.cwd()) {
6236
6277
  const operations = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "permachine",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Automatically merge machine-specific config files with base configs using git hooks",
5
5
  "type": "module",
6
6
  "bin": {