@uniformdev/transformer 1.1.41 → 1.1.43

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/dist/cli/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli/index.ts
4
- import { Command as Command20 } from "commander";
4
+ import { Command as Command21 } from "commander";
5
5
 
6
6
  // src/cli/commands/propagate-root-component-property.ts
7
7
  import { Command } from "commander";
@@ -6829,7 +6829,7 @@ var GenerateMissingProjectMapNodesService = class {
6829
6829
  this.logger = logger;
6830
6830
  }
6831
6831
  async generate(options) {
6832
- const { rootDir, projectMapNodesDir, compositionsDir, rootContentTypes, whatIf, strict } = options;
6832
+ const { rootDir, projectMapNodesDir, whatIf } = options;
6833
6833
  const nodesDirFull = this.fileSystem.resolvePath(rootDir, projectMapNodesDir);
6834
6834
  const nodesDirExists = await this.fileSystem.fileExists(nodesDirFull);
6835
6835
  if (!nodesDirExists) {
@@ -6858,41 +6858,8 @@ var GenerateMissingProjectMapNodesService = class {
6858
6858
  allNodes.push({ filePath, node });
6859
6859
  }
6860
6860
  const existingPaths = new Set(allNodes.map(({ node }) => node.path));
6861
- const compositionsDirFull = this.fileSystem.resolvePath(rootDir, compositionsDir);
6862
- const compositionIdToType = /* @__PURE__ */ new Map();
6863
- const compositionsDirExists = await this.fileSystem.fileExists(compositionsDirFull);
6864
- if (!compositionsDirExists) {
6865
- this.logger.warn(`Compositions directory not found: ${compositionsDir} \u2014 no source nodes will match`);
6866
- } else {
6867
- const compositionFiles = await this.fileSystem.findFiles(
6868
- compositionsDirFull,
6869
- "**/*.{json,yaml,yml}"
6870
- );
6871
- for (const filePath of compositionFiles) {
6872
- let comp;
6873
- try {
6874
- comp = await this.fileSystem.readFile(filePath);
6875
- } catch {
6876
- this.logger.warn(`Could not read composition file: ${filePath} \u2014 skipping`);
6877
- continue;
6878
- }
6879
- if (comp?.composition?._id && comp?.composition?.type) {
6880
- compositionIdToType.set(comp.composition._id, comp.composition.type);
6881
- }
6882
- }
6883
- }
6884
- const sourceNodes = allNodes.filter(({ node }) => {
6885
- if (!node.compositionId) return false;
6886
- const rootType = compositionIdToType.get(node.compositionId);
6887
- if (!rootType) {
6888
- this.logger.warn(
6889
- `No composition found for compositionId "${node.compositionId}" (node path: ${node.path}) \u2014 skipping`
6890
- );
6891
- return false;
6892
- }
6893
- return this.matchesContentType(rootType, rootContentTypes, strict);
6894
- });
6895
- this.logger.info(`${sourceNodes.length} node(s) match root content types`);
6861
+ const sourceNodes = allNodes.filter(({ node }) => !!node.compositionId);
6862
+ this.logger.info(`${sourceNodes.length} node(s) selected as sources`);
6896
6863
  const ancestorPaths = /* @__PURE__ */ new Set();
6897
6864
  for (const { node } of sourceNodes) {
6898
6865
  for (const ancestor of this.computeAncestorPaths(node.path)) {
@@ -6949,29 +6916,192 @@ var GenerateMissingProjectMapNodesService = class {
6949
6916
  const slug = nodePath.split("/").filter((s) => s.length > 0).join("-");
6950
6917
  return `${slug}-node.yaml`;
6951
6918
  }
6952
- matchesContentType(rootType, rootContentTypes, strict) {
6953
- for (const ct of rootContentTypes) {
6954
- if (strict) {
6955
- if (rootType === ct) return true;
6956
- } else {
6957
- if (rootType.toLowerCase() === ct.toLowerCase()) return true;
6958
- }
6959
- }
6960
- return false;
6961
- }
6962
6919
  };
6963
6920
 
6964
6921
  // src/cli/commands/generate-missing-project-map-nodes.ts
6965
6922
  function createGenerateMissingProjectMapNodesCommand() {
6966
6923
  const command = new Command19("generate-missing-project-map-nodes");
6967
6924
  command.description(
6968
- "Creates group project map nodes for ancestor path segments that are missing from the project map. Only paths reachable from compositions whose root type matches --rootContentTypes are considered."
6969
- ).option(
6970
- "--rootContentTypes <types>",
6971
- 'Pipe-separated list of root component types identifying the leaf-page compositions whose ancestor paths should be filled in (e.g. "BlogPost|NewsArticle")'
6972
- ).hook("preAction", (thisCommand) => {
6925
+ "Creates group project map nodes for ancestor path segments that are missing from the project map. All project map nodes with a compositionId are used as sources."
6926
+ ).action(async (opts, cmd) => {
6927
+ const globalOpts = cmd.optsWithGlobals();
6928
+ const options = {
6929
+ ...globalOpts
6930
+ };
6931
+ const logger = new Logger();
6932
+ const fileSystem = new FileSystemService();
6933
+ const service = new GenerateMissingProjectMapNodesService(fileSystem, logger);
6934
+ const result = await service.generate({
6935
+ rootDir: options.rootDir,
6936
+ projectMapNodesDir: options.projectMapNodesDir,
6937
+ whatIf: options.whatIf ?? false
6938
+ });
6939
+ logger.success(
6940
+ `Created ${result.generatedCount} missing project map node(s). ${result.alreadyCoveredCount} ancestor path(s) already existed.`
6941
+ );
6942
+ });
6943
+ return command;
6944
+ }
6945
+
6946
+ // src/cli/commands/split-content-type.ts
6947
+ import { Command as Command20 } from "commander";
6948
+
6949
+ // src/core/services/content-type-splitter.service.ts
6950
+ var ContentTypeSplitterService = class {
6951
+ constructor(fileSystem, logger) {
6952
+ this.fileSystem = fileSystem;
6953
+ this.logger = logger;
6954
+ }
6955
+ async split(options) {
6956
+ const {
6957
+ rootDir,
6958
+ contentTypesDir,
6959
+ entriesDir,
6960
+ contentType,
6961
+ newContentType,
6962
+ fieldId,
6963
+ fieldContains,
6964
+ whatIf,
6965
+ strict
6966
+ } = options;
6967
+ const ctDirFull = this.fileSystem.resolvePath(rootDir, contentTypesDir);
6968
+ const ctDirExists = await this.fileSystem.fileExists(ctDirFull);
6969
+ if (!ctDirExists) {
6970
+ this.logger.warn(`Content types directory not found: ${contentTypesDir}`);
6971
+ return { contentTypeCreated: false, entriesScanned: 0, entriesUpdated: 0 };
6972
+ }
6973
+ const ctFiles = await this.fileSystem.findFiles(ctDirFull, "**/*.{json,yaml,yml}");
6974
+ let sourceCtFilePath;
6975
+ let sourceCt;
6976
+ let newCtExists = false;
6977
+ for (const filePath of ctFiles) {
6978
+ let ct;
6979
+ try {
6980
+ ct = await this.fileSystem.readFile(filePath);
6981
+ } catch {
6982
+ this.logger.warn(`Could not read content type file: ${filePath} \u2014 skipping`);
6983
+ continue;
6984
+ }
6985
+ if (!ct?.id) continue;
6986
+ if (this.matchesId(ct.id, contentType, strict)) {
6987
+ sourceCtFilePath = filePath;
6988
+ sourceCt = ct;
6989
+ }
6990
+ if (this.matchesId(ct.id, newContentType, strict)) {
6991
+ newCtExists = true;
6992
+ }
6993
+ }
6994
+ if (!sourceCtFilePath || !sourceCt) {
6995
+ this.logger.warn(`Content type "${contentType}" not found in ${contentTypesDir}`);
6996
+ return { contentTypeCreated: false, entriesScanned: 0, entriesUpdated: 0 };
6997
+ }
6998
+ let contentTypeCreated = false;
6999
+ if (newCtExists) {
7000
+ this.logger.info(`Content type "${newContentType}" already exists \u2014 skipping creation`);
7001
+ } else {
7002
+ const newCt = {
7003
+ ...sourceCt,
7004
+ id: newContentType,
7005
+ name: newContentType
7006
+ };
7007
+ const ext = this.fileSystem.getExtension(sourceCtFilePath) || ".yaml";
7008
+ const newCtFilePath = this.fileSystem.resolvePath(ctDirFull, `${newContentType}${ext}`);
7009
+ const relPath = `${contentTypesDir}/${newContentType}${ext}`;
7010
+ this.logger.action(whatIf, "CREATE", relPath);
7011
+ if (!whatIf) {
7012
+ await this.fileSystem.writeFile(newCtFilePath, newCt);
7013
+ }
7014
+ contentTypeCreated = true;
7015
+ }
7016
+ const entriesDirFull = this.fileSystem.resolvePath(rootDir, entriesDir);
7017
+ const entriesDirExists = await this.fileSystem.fileExists(entriesDirFull);
7018
+ if (!entriesDirExists) {
7019
+ this.logger.warn(`Entries directory not found: ${entriesDir}`);
7020
+ return { contentTypeCreated, entriesScanned: 0, entriesUpdated: 0 };
7021
+ }
7022
+ const entryFiles = await this.fileSystem.findFiles(entriesDirFull, "**/*.{json,yaml,yml}");
7023
+ this.logger.info(`Scanning ${entryFiles.length} entry file(s)`);
7024
+ let entriesScanned = 0;
7025
+ let entriesUpdated = 0;
7026
+ for (const filePath of entryFiles) {
7027
+ let entryData;
7028
+ try {
7029
+ entryData = await this.fileSystem.readFile(filePath);
7030
+ } catch {
7031
+ this.logger.warn(`Could not read entry file: ${filePath} \u2014 skipping`);
7032
+ continue;
7033
+ }
7034
+ if (!entryData?.entry?.type) continue;
7035
+ if (!this.matchesId(entryData.entry.type, contentType, strict)) continue;
7036
+ entriesScanned++;
7037
+ const fields = entryData.entry.fields;
7038
+ if (!fields || typeof fields !== "object") {
7039
+ this.logger.warn(`Entry "${entryData.entry._id}" has no fields \u2014 skipping`);
7040
+ continue;
7041
+ }
7042
+ if (!(fieldId in fields)) {
7043
+ this.logger.warn(
7044
+ `Entry "${entryData.entry._id}" does not have field "${fieldId}" \u2014 skipping`
7045
+ );
7046
+ continue;
7047
+ }
7048
+ const field = fields[fieldId];
7049
+ const fieldValueStr = this.getFieldValueString(field);
7050
+ if (!this.containsValue(fieldValueStr, fieldContains, strict)) continue;
7051
+ const relPath = this.fileSystem.joinPath(
7052
+ entriesDir,
7053
+ this.fileSystem.getBasename(filePath)
7054
+ );
7055
+ this.logger.action(
7056
+ whatIf,
7057
+ "UPDATE",
7058
+ `${relPath}: type ${entryData.entry.type} \u2192 ${newContentType}`
7059
+ );
7060
+ if (!whatIf) {
7061
+ const updatedEntry = {
7062
+ ...entryData,
7063
+ entry: { ...entryData.entry, type: newContentType }
7064
+ };
7065
+ await this.fileSystem.writeFile(filePath, updatedEntry);
7066
+ }
7067
+ entriesUpdated++;
7068
+ }
7069
+ return { contentTypeCreated, entriesScanned, entriesUpdated };
7070
+ }
7071
+ matchesId(value, target, strict) {
7072
+ if (strict) return value === target;
7073
+ return value.toLowerCase() === target.toLowerCase();
7074
+ }
7075
+ containsValue(haystack, needle, strict) {
7076
+ if (strict) return haystack.includes(needle);
7077
+ return haystack.toLowerCase().includes(needle.toLowerCase());
7078
+ }
7079
+ getFieldValueString(field) {
7080
+ if (field === null || field === void 0) return "";
7081
+ if (typeof field === "string") return field;
7082
+ if (typeof field === "number" || typeof field === "boolean") return String(field);
7083
+ if (typeof field === "object") {
7084
+ const f = field;
7085
+ if (f.value !== void 0) return String(f.value);
7086
+ return JSON.stringify(f);
7087
+ }
7088
+ return String(field);
7089
+ }
7090
+ };
7091
+
7092
+ // src/cli/commands/split-content-type.ts
7093
+ function createSplitContentTypeCommand() {
7094
+ const command = new Command20("split-content-type");
7095
+ command.description(
7096
+ "Creates a new content type as a copy of an existing one, then reassigns matching entries to it."
7097
+ ).option("--contentType <type>", "Source content type ID to split").option("--newContentType <type>", "New content type ID to create").option("--fieldId <id>", "Field ID in entry.fields whose value is checked").option("--fieldContains <value>", "Substring to search for in the field value").hook("preAction", (thisCommand) => {
6973
7098
  const opts = thisCommand.opts();
6974
- const requiredOptions = [{ name: "rootContentTypes", flag: "--rootContentTypes" }];
7099
+ const requiredOptions = [
7100
+ { name: "contentType", flag: "--contentType" },
7101
+ { name: "newContentType", flag: "--newContentType" },
7102
+ { name: "fieldId", flag: "--fieldId" },
7103
+ { name: "fieldContains", flag: "--fieldContains" }
7104
+ ];
6975
7105
  const missing = requiredOptions.filter((opt) => !opts[opt.name]).map((opt) => opt.flag);
6976
7106
  if (missing.length > 0) {
6977
7107
  console.error(`error: missing required options: ${missing.join(", ")}`);
@@ -6981,23 +7111,31 @@ function createGenerateMissingProjectMapNodesCommand() {
6981
7111
  const globalOpts = cmd.optsWithGlobals();
6982
7112
  const options = {
6983
7113
  ...globalOpts,
6984
- rootContentTypes: opts.rootContentTypes
7114
+ contentType: opts.contentType,
7115
+ newContentType: opts.newContentType,
7116
+ fieldId: opts.fieldId,
7117
+ fieldContains: opts.fieldContains
6985
7118
  };
6986
7119
  const logger = new Logger();
6987
- logger.info(`rootContentTypes: ${options.rootContentTypes}`);
6988
- const rootContentTypes = splitList(options.rootContentTypes);
7120
+ logger.info(`contentType: ${options.contentType}`);
7121
+ logger.info(`newContentType: ${options.newContentType}`);
7122
+ logger.info(`fieldId: ${options.fieldId}`);
7123
+ logger.info(`fieldContains: ${options.fieldContains}`);
6989
7124
  const fileSystem = new FileSystemService();
6990
- const service = new GenerateMissingProjectMapNodesService(fileSystem, logger);
6991
- const result = await service.generate({
7125
+ const service = new ContentTypeSplitterService(fileSystem, logger);
7126
+ const result = await service.split({
6992
7127
  rootDir: options.rootDir,
6993
- projectMapNodesDir: options.projectMapNodesDir,
6994
- compositionsDir: options.compositionsDir,
6995
- rootContentTypes,
7128
+ contentTypesDir: options.contentTypesDir,
7129
+ entriesDir: options.entriesDir,
7130
+ contentType: options.contentType,
7131
+ newContentType: options.newContentType,
7132
+ fieldId: options.fieldId,
7133
+ fieldContains: options.fieldContains,
6996
7134
  whatIf: options.whatIf ?? false,
6997
7135
  strict: options.strict ?? false
6998
7136
  });
6999
7137
  logger.success(
7000
- `Created ${result.generatedCount} missing project map node(s). ${result.alreadyCoveredCount} ancestor path(s) already existed.`
7138
+ `Content type created: ${result.contentTypeCreated}. Entries scanned: ${result.entriesScanned}, updated: ${result.entriesUpdated}.`
7001
7139
  );
7002
7140
  });
7003
7141
  return command;
@@ -7006,7 +7144,7 @@ function createGenerateMissingProjectMapNodesCommand() {
7006
7144
  // package.json
7007
7145
  var package_default = {
7008
7146
  name: "@uniformdev/transformer",
7009
- version: "1.1.41",
7147
+ version: "1.1.43",
7010
7148
  description: "CLI tool for transforming Uniform.dev serialization files offline",
7011
7149
  type: "module",
7012
7150
  bin: {
@@ -7075,7 +7213,7 @@ var package_default = {
7075
7213
  };
7076
7214
 
7077
7215
  // src/cli/index.ts
7078
- var program = new Command20();
7216
+ var program = new Command21();
7079
7217
  var appVersion = package_default.version;
7080
7218
  console.error(`uniform-transform v${appVersion}`);
7081
7219
  program.name("uniform-transform").description("CLI tool for transforming Uniform.dev serialization files offline").version(appVersion);
@@ -7110,5 +7248,6 @@ program.addCommand(createRemoveOrphanEntriesCommand());
7110
7248
  program.addCommand(createRemoveUnusedContentTypesCommand());
7111
7249
  program.addCommand(createRemoveUnusedComponentTypesCommand());
7112
7250
  program.addCommand(createGenerateMissingProjectMapNodesCommand());
7251
+ program.addCommand(createSplitContentTypeCommand());
7113
7252
  program.parse();
7114
7253
  //# sourceMappingURL=index.js.map