@embrace-ai/infra-api-schema-sync 1.0.6 → 1.0.8

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.
@@ -0,0 +1,216 @@
1
+ import { execSync } from "child_process";
2
+ import fs from "fs";
3
+ import fse from "fs-extra";
4
+ import path from "path";
5
+
6
+ import { extractServiceFromUrl } from "./generate-schema-url.js";
7
+
8
+ /**
9
+ * Find all schema.graphql files in directories matching the service name
10
+ * @param {string} serviceName - Service name to search for
11
+ * @param {string} rootDir - Root directory to search from (default: cwd)
12
+ * @returns {string[]} Array of file paths
13
+ */
14
+ const findSchemaFiles = (serviceName, rootDir = process.cwd()) => {
15
+ if (!serviceName) {
16
+ console.log("⚠️ No service name provided, skipping schema file search");
17
+ return [];
18
+ }
19
+
20
+ console.log(
21
+ `🔍 Searching for schema files in directories matching: ${serviceName}`,
22
+ );
23
+
24
+ try {
25
+ // Use find command to search for schema.graphql files in directories containing the service name
26
+ // This is more efficient than using a glob library for deep directory searches
27
+ const findCommand = `find . -type d -name "*${serviceName}*" -exec find {} -maxdepth 10 -name "schema.graphql" \\; 2>/dev/null || true`;
28
+
29
+ const output = execSync(findCommand, {
30
+ cwd: rootDir,
31
+ encoding: "utf8",
32
+ maxBuffer: 10 * 1024 * 1024, // 10MB buffer
33
+ });
34
+
35
+ // Parse output and clean up paths
36
+ const files = output
37
+ .split("\n")
38
+ .map((line) => line.trim())
39
+ .filter((line) => line.length > 0)
40
+ .map((line) => line.replace(/^\.\//, "")) // Remove leading ./
41
+ .filter(
42
+ (file) =>
43
+ // Exclude node_modules, dist, and other build directories
44
+ !file.includes("node_modules") &&
45
+ !file.includes("/dist/") &&
46
+ !file.includes("/.git/") &&
47
+ !file.includes("/build/") &&
48
+ !file.includes("/coverage/"),
49
+ );
50
+
51
+ // Remove duplicates
52
+ const uniqueFiles = [...new Set(files)];
53
+
54
+ if (uniqueFiles.length > 0) {
55
+ console.log(`✅ Found ${uniqueFiles.length} schema file(s):`);
56
+ uniqueFiles.forEach((file) => console.log(` - ${file}`));
57
+ } else {
58
+ console.log(
59
+ `ℹ️ No schema files found in directories matching: ${serviceName}`,
60
+ );
61
+ }
62
+
63
+ return uniqueFiles;
64
+ } catch (error) {
65
+ console.warn(`⚠️ Error searching for schema files: ${error.message}`);
66
+ return [];
67
+ }
68
+ };
69
+
70
+ /**
71
+ * Update a schema file with new content
72
+ * @param {string} filePath - Path to the schema file
73
+ * @param {string} newContent - New schema content
74
+ * @returns {boolean} True if file was updated (content changed)
75
+ */
76
+ const updateSchemaFile = (filePath, newContent) => {
77
+ try {
78
+ // Check if file exists and read current content
79
+ let currentContent = "";
80
+ if (fs.existsSync(filePath)) {
81
+ currentContent = fs.readFileSync(filePath, "utf8");
82
+ }
83
+
84
+ // Compare content (trim to ignore trailing whitespace differences)
85
+ if (currentContent.trim() === newContent.trim()) {
86
+ console.log(` ℹ️ No changes needed for: ${filePath}`);
87
+ return false;
88
+ }
89
+
90
+ // Ensure directory exists
91
+ const dir = path.dirname(filePath);
92
+ fse.ensureDirSync(dir);
93
+
94
+ // Write new content
95
+ fs.writeFileSync(filePath, newContent, "utf8");
96
+ console.log(` ✅ Updated: ${filePath}`);
97
+ return true;
98
+ } catch (error) {
99
+ console.error(` ❌ Failed to update ${filePath}: ${error.message}`);
100
+ return false;
101
+ }
102
+ };
103
+
104
+ /**
105
+ * Update local schema files based on schema URL
106
+ * @param {Object} options - Configuration options
107
+ * @param {string} options.schemaUrl - Schema URL to extract service name from
108
+ * @param {string} options.schemaFile - Path to the new schema file
109
+ * @param {string} options.output - JSON output file path
110
+ */
111
+ export const updateLocalSchema = async ({ schemaUrl, schemaFile, output }) => {
112
+ try {
113
+ console.log("🔧 Starting local schema update process...");
114
+ console.log(`📡 Schema URL: ${schemaUrl}`);
115
+ console.log(`📄 New schema file: ${schemaFile}`);
116
+
117
+ // Extract service name from URL
118
+ const serviceName = extractServiceFromUrl(schemaUrl);
119
+
120
+ if (!serviceName) {
121
+ console.log(
122
+ "ℹ️ This is the main embrace API - no service-specific schema files to update",
123
+ );
124
+ const result = {
125
+ serviceName: "embrace",
126
+ updatedFiles: [],
127
+ hasChanges: false,
128
+ message:
129
+ "Main embrace API - no service-specific schema files to update",
130
+ };
131
+
132
+ // Write result to output file
133
+ if (output) {
134
+ fse.ensureDirSync(path.dirname(output));
135
+ fs.writeFileSync(output, JSON.stringify(result, null, 2), "utf8");
136
+ console.log(`📊 Result written to: ${output}`);
137
+ }
138
+
139
+ return result;
140
+ }
141
+
142
+ console.log(`🎯 Service name: ${serviceName}`);
143
+
144
+ // Read new schema content
145
+ if (!fs.existsSync(schemaFile)) {
146
+ throw new Error(`Schema file not found: ${schemaFile}`);
147
+ }
148
+ const newSchemaContent = fs.readFileSync(schemaFile, "utf8");
149
+
150
+ // Find schema files to update
151
+ const schemaFiles = findSchemaFiles(serviceName);
152
+
153
+ if (schemaFiles.length === 0) {
154
+ console.log("⚠️ No schema files found to update");
155
+ const result = {
156
+ serviceName,
157
+ updatedFiles: [],
158
+ hasChanges: false,
159
+ message: `No schema files found in directories matching: ${serviceName}`,
160
+ };
161
+
162
+ // Write result to output file
163
+ if (output) {
164
+ fse.ensureDirSync(path.dirname(output));
165
+ fs.writeFileSync(output, JSON.stringify(result, null, 2), "utf8");
166
+ console.log(`📊 Result written to: ${output}`);
167
+ }
168
+
169
+ return result;
170
+ }
171
+
172
+ // Update each schema file
173
+ console.log(`\n📝 Updating schema files...`);
174
+ const updatedFiles = [];
175
+ for (const file of schemaFiles) {
176
+ const wasUpdated = updateSchemaFile(file, newSchemaContent);
177
+ if (wasUpdated) {
178
+ updatedFiles.push(file);
179
+ }
180
+ }
181
+
182
+ const hasChanges = updatedFiles.length > 0;
183
+
184
+ console.log("");
185
+ if (hasChanges) {
186
+ console.log(`✅ Successfully updated ${updatedFiles.length} file(s)`);
187
+ } else {
188
+ console.log(`ℹ️ All schema files are already up to date`);
189
+ }
190
+
191
+ // Prepare result
192
+ const result = {
193
+ serviceName,
194
+ updatedFiles,
195
+ hasChanges,
196
+ message: hasChanges
197
+ ? `Updated ${updatedFiles.length} schema file(s)`
198
+ : "All schema files are already up to date",
199
+ };
200
+
201
+ // Write result to output file
202
+ if (output) {
203
+ fse.ensureDirSync(path.dirname(output));
204
+ fs.writeFileSync(output, JSON.stringify(result, null, 2), "utf8");
205
+ console.log(`📊 Result written to: ${output}`);
206
+ }
207
+
208
+ return result;
209
+ } catch (error) {
210
+ console.error(`❌ Failed to update local schema: ${error.message}`);
211
+ throw error;
212
+ }
213
+ };
214
+
215
+ // Default export for CLI
216
+ export default updateLocalSchema;
@@ -0,0 +1,233 @@
1
+ import fs from "fs";
2
+ import fse from "fs-extra";
3
+ import path from "path";
4
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
5
+
6
+ import { extractServiceFromUrl } from "./generate-schema-url.js";
7
+ import { updateLocalSchema } from "./update-local-schema.js";
8
+
9
+ describe("extractServiceFromUrl", () => {
10
+ it("should extract service name from dev service URL", () => {
11
+ const url =
12
+ "https://api.integrations-plugins.services.dev.embrace.ai/graphql/schema";
13
+ expect(extractServiceFromUrl(url)).toBe("integrations-plugins");
14
+ });
15
+
16
+ it("should extract service name from prod service URL", () => {
17
+ const url =
18
+ "https://api.integrations-plugins.services.embrace.ai/graphql/schema";
19
+ expect(extractServiceFromUrl(url)).toBe("integrations-plugins");
20
+ });
21
+
22
+ it("should extract service name from PR service URL", () => {
23
+ const url =
24
+ "https://api.integrations-plugins.pr-123.services.dev.embrace.ai/graphql/schema";
25
+ expect(extractServiceFromUrl(url)).toBe("integrations-plugins");
26
+ });
27
+
28
+ it("should return null for main embrace API dev URL", () => {
29
+ const url = "https://api.dev.embrace.ai/v2/graphql/schema";
30
+ expect(extractServiceFromUrl(url)).toBe(null);
31
+ });
32
+
33
+ it("should return null for main embrace API PR URL", () => {
34
+ const url = "https://api.pr-123.dev.embrace.ai/v2/graphql/schema";
35
+ expect(extractServiceFromUrl(url)).toBe(null);
36
+ });
37
+
38
+ it("should handle multi-part service names", () => {
39
+ const url = "https://api.orgs-core.services.dev.embrace.ai/graphql/schema";
40
+ expect(extractServiceFromUrl(url)).toBe("orgs-core");
41
+ });
42
+ });
43
+
44
+ describe("updateLocalSchema", () => {
45
+ const testDir = path.join(process.cwd(), "test-temp-schema-update");
46
+ const schemaContent = `
47
+ type Query {
48
+ hello: String
49
+ }
50
+ `.trim();
51
+
52
+ const updatedSchemaContent = `
53
+ type Query {
54
+ hello: String
55
+ world: String
56
+ }
57
+ `.trim();
58
+
59
+ beforeEach(() => {
60
+ // Create test directory structure
61
+ fse.ensureDirSync(testDir);
62
+ });
63
+
64
+ afterEach(() => {
65
+ // Clean up test directory
66
+ if (fs.existsSync(testDir)) {
67
+ fse.removeSync(testDir);
68
+ }
69
+ });
70
+
71
+ it("should find and update schema files in matching directories", async () => {
72
+ // Create test structure
73
+ const serviceName = "integrations-plugins";
74
+ const schemaDir = path.join(
75
+ testDir,
76
+ "packages/core/src/services",
77
+ serviceName,
78
+ );
79
+ const schemaFile = path.join(schemaDir, "schema.graphql");
80
+
81
+ fse.ensureDirSync(schemaDir);
82
+ fs.writeFileSync(schemaFile, schemaContent, "utf8");
83
+
84
+ // Create new schema file
85
+ const newSchemaFile = path.join(testDir, "new-schema.graphql");
86
+ fs.writeFileSync(newSchemaFile, updatedSchemaContent, "utf8");
87
+
88
+ // Create output file path
89
+ const outputFile = path.join(testDir, "result.json");
90
+
91
+ // Run update
92
+ const result = await updateLocalSchema({
93
+ schemaUrl: `https://api.${serviceName}.services.dev.embrace.ai/graphql/schema`,
94
+ schemaFile: newSchemaFile,
95
+ output: outputFile,
96
+ });
97
+
98
+ // Verify result
99
+ expect(result.serviceName).toBe(serviceName);
100
+ expect(result.hasChanges).toBe(true);
101
+ expect(result.updatedFiles.length).toBeGreaterThan(0);
102
+
103
+ // Verify file was updated
104
+ const updatedContent = fs.readFileSync(schemaFile, "utf8");
105
+ expect(updatedContent.trim()).toBe(updatedSchemaContent);
106
+
107
+ // Verify output file was created
108
+ expect(fs.existsSync(outputFile)).toBe(true);
109
+ const outputData = JSON.parse(fs.readFileSync(outputFile, "utf8"));
110
+ expect(outputData.hasChanges).toBe(true);
111
+
112
+ // Verify the original schema file still exists (updated in place)
113
+ expect(fs.existsSync(schemaFile)).toBe(true);
114
+
115
+ // Verify the temporary input file still exists (not deleted by function)
116
+ expect(fs.existsSync(newSchemaFile)).toBe(true);
117
+ });
118
+
119
+ it("should detect when no changes are needed", async () => {
120
+ // Create test structure
121
+ const serviceName = "integrations-plugins";
122
+ const schemaDir = path.join(
123
+ testDir,
124
+ "packages/core/src/services",
125
+ serviceName,
126
+ );
127
+ const schemaFile = path.join(schemaDir, "schema.graphql");
128
+
129
+ fse.ensureDirSync(schemaDir);
130
+ fs.writeFileSync(schemaFile, schemaContent, "utf8");
131
+
132
+ // Create new schema file with same content
133
+ const newSchemaFile = path.join(testDir, "new-schema.graphql");
134
+ fs.writeFileSync(newSchemaFile, schemaContent, "utf8");
135
+
136
+ // Create output file path
137
+ const outputFile = path.join(testDir, "result.json");
138
+
139
+ // Run update
140
+ const result = await updateLocalSchema({
141
+ schemaUrl: `https://api.${serviceName}.services.dev.embrace.ai/graphql/schema`,
142
+ schemaFile: newSchemaFile,
143
+ output: outputFile,
144
+ });
145
+
146
+ // Verify result
147
+ expect(result.serviceName).toBe(serviceName);
148
+ expect(result.hasChanges).toBe(false);
149
+ expect(result.updatedFiles.length).toBe(0);
150
+
151
+ // Verify the original schema file still exists (unchanged)
152
+ expect(fs.existsSync(schemaFile)).toBe(true);
153
+
154
+ // Verify the temporary input file still exists (not deleted by function)
155
+ expect(fs.existsSync(newSchemaFile)).toBe(true);
156
+ });
157
+
158
+ it("should handle main embrace API (no service name)", async () => {
159
+ // Create new schema file
160
+ const newSchemaFile = path.join(testDir, "new-schema.graphql");
161
+ fs.writeFileSync(newSchemaFile, schemaContent, "utf8");
162
+
163
+ // Create output file path
164
+ const outputFile = path.join(testDir, "result.json");
165
+
166
+ // Run update with main API URL
167
+ const result = await updateLocalSchema({
168
+ schemaUrl: "https://api.dev.embrace.ai/v2/graphql/schema",
169
+ schemaFile: newSchemaFile,
170
+ output: outputFile,
171
+ });
172
+
173
+ // Verify result
174
+ expect(result.serviceName).toBe("embrace");
175
+ expect(result.hasChanges).toBe(false);
176
+ expect(result.updatedFiles.length).toBe(0);
177
+
178
+ // Verify the temporary input file still exists (not deleted by function)
179
+ expect(fs.existsSync(newSchemaFile)).toBe(true);
180
+ });
181
+
182
+ it("should handle multiple schema files in different directories", async () => {
183
+ // Create test structure with multiple schema files
184
+ const serviceName = "integrations-plugins";
185
+
186
+ const schemaDir1 = path.join(
187
+ testDir,
188
+ "packages/core/src/services",
189
+ serviceName,
190
+ );
191
+ const schemaFile1 = path.join(schemaDir1, "schema.graphql");
192
+
193
+ const schemaDir2 = path.join(testDir, "apps", serviceName, "graphql");
194
+ const schemaFile2 = path.join(schemaDir2, "schema.graphql");
195
+
196
+ fse.ensureDirSync(schemaDir1);
197
+ fse.ensureDirSync(schemaDir2);
198
+ fs.writeFileSync(schemaFile1, schemaContent, "utf8");
199
+ fs.writeFileSync(schemaFile2, schemaContent, "utf8");
200
+
201
+ // Create new schema file
202
+ const newSchemaFile = path.join(testDir, "new-schema.graphql");
203
+ fs.writeFileSync(newSchemaFile, updatedSchemaContent, "utf8");
204
+
205
+ // Create output file path
206
+ const outputFile = path.join(testDir, "result.json");
207
+
208
+ // Run update
209
+ const result = await updateLocalSchema({
210
+ schemaUrl: `https://api.${serviceName}.services.dev.embrace.ai/graphql/schema`,
211
+ schemaFile: newSchemaFile,
212
+ output: outputFile,
213
+ });
214
+
215
+ // Verify result
216
+ expect(result.serviceName).toBe(serviceName);
217
+ expect(result.hasChanges).toBe(true);
218
+ expect(result.updatedFiles.length).toBeGreaterThanOrEqual(1);
219
+
220
+ // Verify both files were updated
221
+ const updatedContent1 = fs.readFileSync(schemaFile1, "utf8");
222
+ const updatedContent2 = fs.readFileSync(schemaFile2, "utf8");
223
+ expect(updatedContent1.trim()).toBe(updatedSchemaContent);
224
+ expect(updatedContent2.trim()).toBe(updatedSchemaContent);
225
+
226
+ // Verify both original schema files still exist (updated in place)
227
+ expect(fs.existsSync(schemaFile1)).toBe(true);
228
+ expect(fs.existsSync(schemaFile2)).toBe(true);
229
+
230
+ // Verify the temporary input file still exists (not deleted by function)
231
+ expect(fs.existsSync(newSchemaFile)).toBe(true);
232
+ });
233
+ });
@@ -45,7 +45,7 @@ export const validateSchema = async ({ schema, queries, output }) => {
45
45
  const invalidDocuments = validate(graphqlSchema, sources, {
46
46
  strictDeprecated: false, // Don't fail on deprecated usage by default
47
47
  strictFragments: true,
48
- apollo: false,
48
+ apollo: true, // Enable Apollo Federation directive support
49
49
  });
50
50
 
51
51
  // Transform the results to match expected output format