@wictorwilen/cocogen 1.0.0 → 1.0.11
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/README.md +88 -35
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +84 -14
- package/dist/cli.js.map +1 -1
- package/dist/init/init.d.ts.map +1 -1
- package/dist/init/init.js +302 -156
- package/dist/init/init.js.map +1 -1
- package/dist/init/templates/dotnet/AGENTS.md.ejs +20 -0
- package/dist/init/templates/dotnet/Core/ConnectorCore.cs.ejs +602 -0
- package/dist/init/templates/dotnet/Datasource/CsvItemSource.cs.ejs +12 -3
- package/dist/init/templates/dotnet/Datasource/IItemSource.cs.ejs +10 -4
- package/dist/init/templates/dotnet/Generated/Constants.cs.ejs +5 -1
- package/dist/init/templates/dotnet/Generated/CsvParser.cs.ejs +61 -0
- package/dist/init/templates/dotnet/Generated/FromCsvRow.cs.ejs +11 -3
- package/dist/init/templates/dotnet/Generated/ItemPayload.cs.ejs +13 -3
- package/dist/init/templates/dotnet/Generated/Model.cs.ejs +5 -22
- package/dist/init/templates/dotnet/Generated/PropertyTransformBase.cs.ejs +45 -0
- package/dist/init/templates/dotnet/Generated/SchemaPayload.cs.ejs +9 -1
- package/dist/init/templates/dotnet/Program.commandline.cs.ejs +76 -278
- package/dist/init/templates/dotnet/PropertyTransform.cs.ejs +10 -0
- package/dist/init/templates/dotnet/README.md.ejs +6 -7
- package/dist/init/templates/dotnet/appsettings.json.ejs +1 -1
- package/dist/init/templates/ts/.env.example.ejs +1 -1
- package/dist/init/templates/ts/AGENTS.md.ejs +20 -0
- package/dist/init/templates/ts/README.md.ejs +6 -7
- package/dist/init/templates/ts/src/cli.ts.ejs +85 -173
- package/dist/init/templates/ts/src/core/connectorCore.ts.ejs +384 -0
- package/dist/init/templates/ts/src/datasource/csvItemSource.ts.ejs +12 -4
- package/dist/init/templates/ts/src/datasource/itemSource.ts.ejs +12 -5
- package/dist/init/templates/ts/src/generated/constants.ts.ejs +16 -0
- package/dist/init/templates/ts/src/generated/csv.ts.ejs +10 -0
- package/dist/init/templates/ts/src/generated/fromCsvRow.ts.ejs +12 -37
- package/dist/init/templates/ts/src/generated/index.ts.ejs +3 -0
- package/dist/init/templates/ts/src/generated/itemPayload.ts.ejs +12 -3
- package/dist/init/templates/ts/src/generated/model.ts.ejs +3 -11
- package/dist/init/templates/ts/src/generated/propertyTransformBase.ts.ejs +40 -0
- package/dist/init/templates/ts/src/generated/schemaPayload.ts.ejs +3 -0
- package/dist/init/templates/ts/src/index.ts.ejs +4 -1
- package/dist/init/templates/ts/src/propertyTransform.ts.ejs +16 -0
- package/dist/ir.d.ts +2 -0
- package/dist/ir.d.ts.map +1 -1
- package/dist/tsp/init-tsp.d.ts.map +1 -1
- package/dist/tsp/init-tsp.js +50 -7
- package/dist/tsp/init-tsp.js.map +1 -1
- package/dist/tsp/loader.d.ts.map +1 -1
- package/dist/tsp/loader.js +23 -9
- package/dist/tsp/loader.js.map +1 -1
- package/dist/typespec/decorators.d.ts +1 -0
- package/dist/typespec/decorators.d.ts.map +1 -1
- package/dist/typespec/decorators.js +7 -2
- package/dist/typespec/decorators.js.map +1 -1
- package/dist/typespec/state.d.ts +2 -0
- package/dist/typespec/state.d.ts.map +1 -1
- package/dist/typespec/state.js +1 -0
- package/dist/typespec/state.js.map +1 -1
- package/dist/validate/validator.d.ts.map +1 -1
- package/dist/validate/validator.js +127 -14
- package/dist/validate/validator.js.map +1 -1
- package/package.json +2 -1
- package/typespec/main.tsp +6 -2
- package/dist/init/templates/dotnet/Generated/PersonEntityDefaults.cs.ejs +0 -48
- package/dist/init/templates/dotnet/Generated/PropertyTransforms.cs.ejs +0 -22
- package/dist/init/templates/dotnet/PersonEntityOverrides.cs.ejs +0 -49
- package/dist/init/templates/dotnet/Program.cs.ejs +0 -487
- package/dist/init/templates/ts/src/generated/personEntityDefaults.ts.ejs +0 -33
- package/dist/init/templates/ts/src/generated/propertyTransforms.ts.ejs +0 -23
- package/dist/init/templates/ts/src/personEntityOverrides.ts.ejs +0 -36
|
@@ -1,487 +0,0 @@
|
|
|
1
|
-
using Azure.Identity;
|
|
2
|
-
using Azure.Core;
|
|
3
|
-
using Microsoft.Graph;
|
|
4
|
-
using Microsoft.Graph.Models.ExternalConnectors;
|
|
5
|
-
using Microsoft.Graph.Models.ODataErrors;
|
|
6
|
-
using Microsoft.Kiota.Abstractions;
|
|
7
|
-
using Microsoft.Kiota.Abstractions.Serialization;
|
|
8
|
-
using System.Net.Http.Headers;
|
|
9
|
-
using System.Text;
|
|
10
|
-
using System.Text.Json;
|
|
11
|
-
|
|
12
|
-
using <%= namespaceName %>.Datasource;
|
|
13
|
-
using <%= namespaceName %>.Generated;
|
|
14
|
-
|
|
15
|
-
var command = args.Length > 0 ? args[0] : "";
|
|
16
|
-
|
|
17
|
-
static string RequiredEnv(string name)
|
|
18
|
-
{
|
|
19
|
-
var value = Environment.GetEnvironmentVariable(name);
|
|
20
|
-
if (string.IsNullOrWhiteSpace(value))
|
|
21
|
-
throw new InvalidOperationException($"Missing env var: {name}");
|
|
22
|
-
return value;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
static GraphServiceClient CreateGraphClient()
|
|
26
|
-
{
|
|
27
|
-
var credential = CreateCredential();
|
|
28
|
-
|
|
29
|
-
var graph = new GraphServiceClient(credential, new[] { "https://graph.microsoft.com/.default" });
|
|
30
|
-
graph.RequestAdapter.BaseUrl = SchemaConstants.GraphBaseUrl;
|
|
31
|
-
return graph;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
static ClientSecretCredential CreateCredential()
|
|
35
|
-
{
|
|
36
|
-
var tenantId = RequiredEnv("TENANT_ID");
|
|
37
|
-
var clientId = RequiredEnv("CLIENT_ID");
|
|
38
|
-
var clientSecret = RequiredEnv("CLIENT_SECRET");
|
|
39
|
-
return new ClientSecretCredential(tenantId, clientId, clientSecret);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
static async Task<string> GetAccessTokenAsync(ClientSecretCredential credential)
|
|
43
|
-
{
|
|
44
|
-
var token = await credential.GetTokenAsync(
|
|
45
|
-
new TokenRequestContext(new[] { "https://graph.microsoft.com/.default" })
|
|
46
|
-
);
|
|
47
|
-
return token.Token;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
static async Task<HttpResponseMessage> GraphRequestAsync(ClientSecretCredential credential, HttpMethod method, string url, object? body = null)
|
|
51
|
-
{
|
|
52
|
-
using var http = new HttpClient();
|
|
53
|
-
var token = await GetAccessTokenAsync(credential);
|
|
54
|
-
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
|
55
|
-
|
|
56
|
-
var request = new HttpRequestMessage(method, url);
|
|
57
|
-
if (body is not null)
|
|
58
|
-
{
|
|
59
|
-
var json = JsonSerializer.Serialize(body);
|
|
60
|
-
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return await http.SendAsync(request);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
static bool IsStatus(ApiException ex, int statusCode)
|
|
67
|
-
{
|
|
68
|
-
return ex.ResponseStatusCode is int sc && sc == statusCode;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
static async Task EnsureConnectionAsync(GraphServiceClient graph, string connectionId)
|
|
72
|
-
{
|
|
73
|
-
var name = RequiredEnv("CONNECTION_NAME");
|
|
74
|
-
var description = RequiredEnv("CONNECTION_DESCRIPTION");
|
|
75
|
-
|
|
76
|
-
try
|
|
77
|
-
{
|
|
78
|
-
await graph.External.Connections[connectionId].GetAsync();
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
using Azure.Core;
|
|
82
|
-
using Azure.Identity;
|
|
83
|
-
using Microsoft.Extensions.Configuration;
|
|
84
|
-
using Microsoft.Graph;
|
|
85
|
-
using Microsoft.Graph.Models.ExternalConnectors;
|
|
86
|
-
using Microsoft.Graph.Models.ODataErrors;
|
|
87
|
-
using Microsoft.Kiota.Abstractions;
|
|
88
|
-
using Microsoft.Kiota.Abstractions.Serialization;
|
|
89
|
-
using System.CommandLine;
|
|
90
|
-
using System.Net.Http.Headers;
|
|
91
|
-
using System.Text;
|
|
92
|
-
using System.Text.Json;
|
|
93
|
-
|
|
94
|
-
using <%= namespaceName %>.Datasource;
|
|
95
|
-
using <%= namespaceName %>.Schema;
|
|
96
|
-
|
|
97
|
-
var configuration = new ConfigurationBuilder()
|
|
98
|
-
.SetBasePath(Directory.GetCurrentDirectory())
|
|
99
|
-
.AddJsonFile("appsettings.json", optional: true)
|
|
100
|
-
.AddJsonFile("appsettings.Development.json", optional: true)
|
|
101
|
-
.AddEnvironmentVariables()
|
|
102
|
-
.Build();
|
|
103
|
-
|
|
104
|
-
string RequiredSetting(string key)
|
|
105
|
-
{
|
|
106
|
-
var value = configuration[key];
|
|
107
|
-
if (string.IsNullOrWhiteSpace(value))
|
|
108
|
-
throw new InvalidOperationException($"Missing configuration: {key}");
|
|
109
|
-
return value;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
string ConnectionId() => RequiredSetting("Connection:Id");
|
|
113
|
-
string ConnectionName() => RequiredSetting("Connection:Name");
|
|
114
|
-
string ConnectionDescription() => RequiredSetting("Connection:Description");
|
|
115
|
-
|
|
116
|
-
ClientSecretCredential CreateCredential()
|
|
117
|
-
{
|
|
118
|
-
var tenantId = RequiredSetting("AzureAd:TenantId");
|
|
119
|
-
var clientId = RequiredSetting("AzureAd:ClientId");
|
|
120
|
-
var clientSecret = RequiredSetting("AzureAd:ClientSecret");
|
|
121
|
-
return new ClientSecretCredential(tenantId, clientId, clientSecret);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
GraphServiceClient CreateGraphClient()
|
|
125
|
-
{
|
|
126
|
-
var credential = CreateCredential();
|
|
127
|
-
var graph = new GraphServiceClient(credential, new[] { "https://graph.microsoft.com/.default" });
|
|
128
|
-
graph.RequestAdapter.BaseUrl = SchemaConstants.GraphBaseUrl;
|
|
129
|
-
return graph;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
async Task<string> GetAccessTokenAsync(ClientSecretCredential credential)
|
|
133
|
-
{
|
|
134
|
-
var token = await credential.GetTokenAsync(
|
|
135
|
-
new TokenRequestContext(new[] { "https://graph.microsoft.com/.default" })
|
|
136
|
-
);
|
|
137
|
-
return token.Token;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
async Task<HttpResponseMessage> GraphRequestAsync(ClientSecretCredential credential, HttpMethod method, string url, object? body = null)
|
|
141
|
-
{
|
|
142
|
-
using var http = new HttpClient();
|
|
143
|
-
var token = await GetAccessTokenAsync(credential);
|
|
144
|
-
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
|
145
|
-
|
|
146
|
-
var request = new HttpRequestMessage(method, url);
|
|
147
|
-
if (body is not null)
|
|
148
|
-
{
|
|
149
|
-
var json = JsonSerializer.Serialize(body);
|
|
150
|
-
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return await http.SendAsync(request);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
bool IsStatus(ApiException ex, int statusCode)
|
|
157
|
-
{
|
|
158
|
-
return ex.ResponseStatusCode is int sc && sc == statusCode;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
async Task EnsureConnectionAsync(GraphServiceClient graph, string connectionId)
|
|
162
|
-
{
|
|
163
|
-
try
|
|
164
|
-
{
|
|
165
|
-
await graph.External.Connections[connectionId].GetAsync();
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
catch (ApiException ex) when (IsStatus(ex, 404))
|
|
169
|
-
{
|
|
170
|
-
// Create below.
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
var connection = new ExternalConnection
|
|
174
|
-
{
|
|
175
|
-
Id = connectionId,
|
|
176
|
-
Name = ConnectionName(),
|
|
177
|
-
Description = ConnectionDescription(),
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
if (!string.IsNullOrWhiteSpace(SchemaConstants.ContentCategory))
|
|
181
|
-
{
|
|
182
|
-
// contentCategory is currently only exposed on the /beta endpoint.
|
|
183
|
-
connection.AdditionalData ??= new Dictionary<string, object>();
|
|
184
|
-
connection.AdditionalData["contentCategory"] = SchemaConstants.ContentCategory!;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
await graph.External.Connections.PostAsync(connection);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
async Task PatchSchemaAsync(GraphServiceClient graph, string connectionId)
|
|
191
|
-
{
|
|
192
|
-
var schema = SchemaPayload.BuildSchema();
|
|
193
|
-
await graph.External.Connections[connectionId].Schema.PatchAsync(schema);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
async Task ProvisionAsync()
|
|
197
|
-
{
|
|
198
|
-
var graph = CreateGraphClient();
|
|
199
|
-
var connectionId = ConnectionId();
|
|
200
|
-
|
|
201
|
-
await EnsureConnectionAsync(graph, connectionId);
|
|
202
|
-
await PatchSchemaAsync(graph, connectionId);
|
|
203
|
-
<% if (isPeopleConnector) { -%>
|
|
204
|
-
await RegisterProfileSourceAsync(connectionId);
|
|
205
|
-
<% } -%>
|
|
206
|
-
|
|
207
|
-
Console.WriteLine("ok: provisioned");
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
async Task PutItemAsync(GraphServiceClient graph, string connectionId, Item item)
|
|
211
|
-
{
|
|
212
|
-
var itemId = ItemPayload.GetItemId(item);
|
|
213
|
-
|
|
214
|
-
var externalItem = ItemPayload.ToExternalItem(item);
|
|
215
|
-
|
|
216
|
-
// NOTE: The external connectors surface may not expose a typed PutAsync on all SDK versions.
|
|
217
|
-
// We still use the Graph SDK's request adapter and models for a strongly-typed payload.
|
|
218
|
-
var requestInfo = new RequestInformation
|
|
219
|
-
{
|
|
220
|
-
HttpMethod = Method.PUT,
|
|
221
|
-
UrlTemplate = "{+baseurl}/external/connections/{connectionId}/items/{itemId}",
|
|
222
|
-
PathParameters = new Dictionary<string, object>
|
|
223
|
-
{
|
|
224
|
-
{ "baseurl", graph.RequestAdapter.BaseUrl },
|
|
225
|
-
{ "connectionId", connectionId },
|
|
226
|
-
{ "itemId", itemId },
|
|
227
|
-
},
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
requestInfo.Headers.Add("Accept", "application/json");
|
|
231
|
-
requestInfo.SetContentFromParsable(graph.RequestAdapter, "application/json", externalItem);
|
|
232
|
-
|
|
233
|
-
var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
|
|
234
|
-
{
|
|
235
|
-
{ "4XX", ODataError.CreateFromDiscriminatorValue },
|
|
236
|
-
{ "5XX", ODataError.CreateFromDiscriminatorValue },
|
|
237
|
-
};
|
|
238
|
-
|
|
239
|
-
await graph.RequestAdapter.SendAsync<ExternalItem>(
|
|
240
|
-
requestInfo,
|
|
241
|
-
ExternalItem.CreateFromDiscriminatorValue,
|
|
242
|
-
errorMapping
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
async Task IngestAsync(string? csvPath)
|
|
247
|
-
{
|
|
248
|
-
var graph = CreateGraphClient();
|
|
249
|
-
var connectionId = ConnectionId();
|
|
250
|
-
|
|
251
|
-
var configuredCsv = configuration["Csv:Path"] ?? "data.csv";
|
|
252
|
-
var path = string.IsNullOrWhiteSpace(csvPath) ? configuredCsv : csvPath;
|
|
253
|
-
|
|
254
|
-
IItemSource source = new CsvItemSource(path);
|
|
255
|
-
|
|
256
|
-
var count = 0;
|
|
257
|
-
await foreach (var item in source.GetItemsAsync())
|
|
258
|
-
{
|
|
259
|
-
await PutItemAsync(graph, connectionId, item);
|
|
260
|
-
count++;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
Console.WriteLine($"ok: ingested {count} item(s)");
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
<% if (isPeopleConnector) { -%>
|
|
267
|
-
async Task RegisterProfileSourceAsync(string connectionId)
|
|
268
|
-
{
|
|
269
|
-
var credential = CreateCredential();
|
|
270
|
-
var webUrl = RequiredSetting("ProfileSource:WebUrl");
|
|
271
|
-
var displayName = configuration["ProfileSource:DisplayName"] ?? ConnectionName();
|
|
272
|
-
var kind = configuration["ProfileSource:Kind"];
|
|
273
|
-
|
|
274
|
-
var payload = new Dictionary<string, object?>
|
|
275
|
-
{
|
|
276
|
-
["sourceId"] = connectionId,
|
|
277
|
-
["displayName"] = displayName,
|
|
278
|
-
["webUrl"] = webUrl,
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
if (!string.IsNullOrWhiteSpace(kind))
|
|
282
|
-
payload["kind"] = kind;
|
|
283
|
-
|
|
284
|
-
var create = await GraphRequestAsync(
|
|
285
|
-
credential,
|
|
286
|
-
HttpMethod.Post,
|
|
287
|
-
$"{SchemaConstants.GraphBaseUrl}/admin/people/profileSources",
|
|
288
|
-
payload
|
|
289
|
-
);
|
|
290
|
-
|
|
291
|
-
if (!create.IsSuccessStatusCode && create.StatusCode != System.Net.HttpStatusCode.Conflict)
|
|
292
|
-
{
|
|
293
|
-
var text = await create.Content.ReadAsStringAsync();
|
|
294
|
-
throw new InvalidOperationException($"Failed to register profile source (HTTP {(int)create.StatusCode}): {text}");
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
var sourceUrl = $"{SchemaConstants.GraphBaseUrl}/admin/people/profileSources(sourceId='{connectionId}')";
|
|
298
|
-
await UpdateProfileSourcePrecedenceAsync(credential, sourceUrl, prepend: true);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
async Task UnregisterProfileSourceAsync(string connectionId)
|
|
302
|
-
{
|
|
303
|
-
var credential = CreateCredential();
|
|
304
|
-
var sourceUrl = $"{SchemaConstants.GraphBaseUrl}/admin/people/profileSources(sourceId='{connectionId}')";
|
|
305
|
-
|
|
306
|
-
await UpdateProfileSourcePrecedenceAsync(credential, sourceUrl, prepend: false);
|
|
307
|
-
|
|
308
|
-
var res = await GraphRequestAsync(
|
|
309
|
-
credential,
|
|
310
|
-
HttpMethod.Delete,
|
|
311
|
-
$"{SchemaConstants.GraphBaseUrl}/admin/people/profileSources(sourceId='{connectionId}')"
|
|
312
|
-
);
|
|
313
|
-
|
|
314
|
-
if (!res.IsSuccessStatusCode && res.StatusCode != System.Net.HttpStatusCode.NotFound)
|
|
315
|
-
{
|
|
316
|
-
var text = await res.Content.ReadAsStringAsync();
|
|
317
|
-
throw new InvalidOperationException($"Failed to delete profile source (HTTP {(int)res.StatusCode}): {text}");
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
async Task UpdateProfileSourcePrecedenceAsync(ClientSecretCredential credential, string sourceUrl, bool prepend)
|
|
322
|
-
{
|
|
323
|
-
var res = await GraphRequestAsync(
|
|
324
|
-
credential,
|
|
325
|
-
HttpMethod.Get,
|
|
326
|
-
$"{SchemaConstants.GraphBaseUrl}/admin/people/profilePropertySettings"
|
|
327
|
-
);
|
|
328
|
-
|
|
329
|
-
if (!res.IsSuccessStatusCode)
|
|
330
|
-
{
|
|
331
|
-
var text = await res.Content.ReadAsStringAsync();
|
|
332
|
-
throw new InvalidOperationException($"Failed to list profile property settings (HTTP {(int)res.StatusCode}): {text}");
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
var json = await res.Content.ReadAsStringAsync();
|
|
336
|
-
using var doc = JsonDocument.Parse(json);
|
|
337
|
-
if (!doc.RootElement.TryGetProperty("value", out var values) || values.ValueKind != JsonValueKind.Array)
|
|
338
|
-
return;
|
|
339
|
-
|
|
340
|
-
foreach (var entry in values.EnumerateArray())
|
|
341
|
-
{
|
|
342
|
-
if (!entry.TryGetProperty("id", out var idProp) || idProp.ValueKind != JsonValueKind.String)
|
|
343
|
-
continue;
|
|
344
|
-
|
|
345
|
-
var id = idProp.GetString() ?? string.Empty;
|
|
346
|
-
if (string.IsNullOrWhiteSpace(id)) continue;
|
|
347
|
-
|
|
348
|
-
List<string> existing = new();
|
|
349
|
-
if (entry.TryGetProperty("prioritizedSourceUrls", out var urls) && urls.ValueKind == JsonValueKind.Array)
|
|
350
|
-
{
|
|
351
|
-
foreach (var url in urls.EnumerateArray())
|
|
352
|
-
{
|
|
353
|
-
if (url.ValueKind == JsonValueKind.String)
|
|
354
|
-
existing.Add(url.GetString() ?? "");
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
existing = existing.Where((u) => !string.IsNullOrWhiteSpace(u) && u != sourceUrl).ToList();
|
|
359
|
-
var updated = prepend ? new[] { sourceUrl }.Concat(existing).ToList() : existing;
|
|
360
|
-
|
|
361
|
-
var patch = new Dictionary<string, object?>
|
|
362
|
-
{
|
|
363
|
-
["@odata.type"] = "#microsoft.graph.profilePropertySetting",
|
|
364
|
-
["prioritizedSourceUrls"] = updated,
|
|
365
|
-
};
|
|
366
|
-
|
|
367
|
-
var patchRes = await GraphRequestAsync(
|
|
368
|
-
credential,
|
|
369
|
-
HttpMethod.Patch,
|
|
370
|
-
$"{SchemaConstants.GraphBaseUrl}/admin/people/profilePropertySettings/{id}",
|
|
371
|
-
patch
|
|
372
|
-
);
|
|
373
|
-
|
|
374
|
-
if (!patchRes.IsSuccessStatusCode)
|
|
375
|
-
{
|
|
376
|
-
var text = await patchRes.Content.ReadAsStringAsync();
|
|
377
|
-
throw new InvalidOperationException($"Failed to update profile property setting {id} (HTTP {(int)patchRes.StatusCode}): {text}");
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
<% } -%>
|
|
382
|
-
|
|
383
|
-
async Task DeleteConnectionAsync()
|
|
384
|
-
{
|
|
385
|
-
var graph = CreateGraphClient();
|
|
386
|
-
var connectionId = ConnectionId();
|
|
387
|
-
|
|
388
|
-
<% if (isPeopleConnector) { -%>
|
|
389
|
-
await UnregisterProfileSourceAsync(connectionId);
|
|
390
|
-
<% } -%>
|
|
391
|
-
|
|
392
|
-
try
|
|
393
|
-
{
|
|
394
|
-
await graph.External.Connections[connectionId].DeleteAsync();
|
|
395
|
-
}
|
|
396
|
-
catch (ApiException ex) when (IsStatus(ex, 404))
|
|
397
|
-
{
|
|
398
|
-
// Already deleted.
|
|
399
|
-
}
|
|
400
|
-
Console.WriteLine("ok: deleted");
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
var csvOption = new Option<string>("--csv", description: "CSV path");
|
|
404
|
-
|
|
405
|
-
var root = new RootCommand("Connector CLI generated by gcgen");
|
|
406
|
-
|
|
407
|
-
var provisionCommand = new Command("provision", "Create or update the connection and schema");
|
|
408
|
-
provisionCommand.SetHandler(async () => await ProvisionAsync());
|
|
409
|
-
|
|
410
|
-
var ingestCommand = new Command("ingest", "Ingest items from CSV");
|
|
411
|
-
ingestCommand.AddOption(csvOption);
|
|
412
|
-
ingestCommand.SetHandler(async (string? csv) => await IngestAsync(csv), csvOption);
|
|
413
|
-
|
|
414
|
-
var deleteCommand = new Command("delete", "Delete the connection");
|
|
415
|
-
deleteCommand.SetHandler(async () => await DeleteConnectionAsync());
|
|
416
|
-
|
|
417
|
-
root.AddCommand(provisionCommand);
|
|
418
|
-
root.AddCommand(ingestCommand);
|
|
419
|
-
root.AddCommand(deleteCommand);
|
|
420
|
-
|
|
421
|
-
<% if (isPeopleConnector) { -%>
|
|
422
|
-
var registerCommand = new Command("register-profile-source", "Register the connection as a profile source");
|
|
423
|
-
registerCommand.SetHandler(async () => await RegisterProfileSourceAsync(ConnectionId()));
|
|
424
|
-
root.AddCommand(registerCommand);
|
|
425
|
-
<% } -%>
|
|
426
|
-
|
|
427
|
-
return await root.InvokeAsync(args);
|
|
428
|
-
{
|
|
429
|
-
var graph = CreateGraphClient();
|
|
430
|
-
var connectionId = RequiredEnv("CONNECTION_ID");
|
|
431
|
-
|
|
432
|
-
var csvArg = GetArgValue("--csv", allArgs);
|
|
433
|
-
var csvPath = !string.IsNullOrWhiteSpace(csvArg)
|
|
434
|
-
? csvArg
|
|
435
|
-
: (Environment.GetEnvironmentVariable("CSV_PATH") ?? "data.csv");
|
|
436
|
-
|
|
437
|
-
IItemSource source = new CsvItemSource(csvPath);
|
|
438
|
-
|
|
439
|
-
var count = 0;
|
|
440
|
-
await foreach (var item in source.GetItemsAsync())
|
|
441
|
-
{
|
|
442
|
-
await PutItemAsync(graph, connectionId, item);
|
|
443
|
-
count++;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
Console.WriteLine($"ok: ingested {count} item(s)");
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
try
|
|
450
|
-
{
|
|
451
|
-
if (command == "provision")
|
|
452
|
-
{
|
|
453
|
-
await ProvisionAsync();
|
|
454
|
-
}
|
|
455
|
-
else if (command == "ingest")
|
|
456
|
-
{
|
|
457
|
-
await IngestAsync(args);
|
|
458
|
-
}
|
|
459
|
-
else if (command == "register-profile-source")
|
|
460
|
-
{
|
|
461
|
-
await RegisterProfileSourceAsync(RequiredEnv("CONNECTION_ID"));
|
|
462
|
-
}
|
|
463
|
-
else if (command == "delete")
|
|
464
|
-
{
|
|
465
|
-
await DeleteConnectionAsync();
|
|
466
|
-
}
|
|
467
|
-
else
|
|
468
|
-
{
|
|
469
|
-
Console.WriteLine("Usage:");
|
|
470
|
-
Console.WriteLine(" dotnet run -- provision");
|
|
471
|
-
Console.WriteLine(" dotnet run -- ingest --csv path/to.csv");
|
|
472
|
-
Console.WriteLine(" dotnet run -- register-profile-source");
|
|
473
|
-
Console.WriteLine(" dotnet run -- delete");
|
|
474
|
-
Environment.ExitCode = 1;
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
catch (ApiException ex)
|
|
478
|
-
{
|
|
479
|
-
Console.Error.WriteLine("error: Graph request failed");
|
|
480
|
-
Console.Error.WriteLine(ex.Message);
|
|
481
|
-
Environment.ExitCode = 1;
|
|
482
|
-
}
|
|
483
|
-
catch (Exception ex)
|
|
484
|
-
{
|
|
485
|
-
Console.Error.WriteLine("error: " + ex.Message);
|
|
486
|
-
Environment.ExitCode = 1;
|
|
487
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// Auto-generated defaults for people connector entity payloads.
|
|
2
|
-
// These are derived from @coco.source mappings and regenerated on every update.
|
|
3
|
-
// Customize output in personEntityOverrides.ts instead.
|
|
4
|
-
|
|
5
|
-
import type { Item } from "./model.js";
|
|
6
|
-
import { parseString, parseStringCollection, readSourceValue } from "../datasource/csv.js";
|
|
7
|
-
|
|
8
|
-
export function buildPersonEntityDefault(name: string, row: Record<string, unknown>): string {
|
|
9
|
-
switch (name) {
|
|
10
|
-
<% for (const def of defaults) { -%>
|
|
11
|
-
<% if (!def.isCollection) { -%>
|
|
12
|
-
case <%= JSON.stringify(def.name) %>:
|
|
13
|
-
return <%- def.expression %>;
|
|
14
|
-
<% } -%>
|
|
15
|
-
<% } -%>
|
|
16
|
-
default:
|
|
17
|
-
return "";
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function buildPersonEntityDefaultCollection(name: string, row: Record<string, unknown>): string[] {
|
|
22
|
-
switch (name) {
|
|
23
|
-
<% for (const def of defaults) { -%>
|
|
24
|
-
<% if (def.isCollection) { -%>
|
|
25
|
-
case <%= JSON.stringify(def.name) %>: {
|
|
26
|
-
return <%- def.expression %>;
|
|
27
|
-
}
|
|
28
|
-
<% } -%>
|
|
29
|
-
<% } -%>
|
|
30
|
-
default:
|
|
31
|
-
return [];
|
|
32
|
-
}
|
|
33
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
// Property transforms for custom mapping logic.
|
|
2
|
-
// For explicit @coco.source mappings, the function receives the source column value.
|
|
3
|
-
// When no @coco.source is provided, the function receives the full row.
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
parseBoolean,
|
|
7
|
-
parseNumber,
|
|
8
|
-
parseNumberCollection,
|
|
9
|
-
parseString,
|
|
10
|
-
parseStringCollection,
|
|
11
|
-
readSourceValue
|
|
12
|
-
} from "../datasource/csv.js";
|
|
13
|
-
|
|
14
|
-
<% for (const prop of properties) { -%>
|
|
15
|
-
export function transform<%= prop.transformName %>(<%= prop.isExplicitSource ? "value: unknown" : "row: Record<string, unknown>" %>): <%= prop.tsType %> {
|
|
16
|
-
// TODO: customize mapping for <%= prop.name %>
|
|
17
|
-
return <%- prop.isExplicitSource
|
|
18
|
-
? `${prop.parser}(value)`
|
|
19
|
-
: `${prop.parser}(readSourceValue(row, ${JSON.stringify(prop.csvHeaders)}))`
|
|
20
|
-
%>;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
<% } -%>
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
// User-editable overrides for people connector entity mapping.
|
|
2
|
-
// This file is created once and will not be overwritten by cocogen update.
|
|
3
|
-
|
|
4
|
-
export function transformPersonEntity(
|
|
5
|
-
name: string,
|
|
6
|
-
row: Record<string, unknown>,
|
|
7
|
-
value: string
|
|
8
|
-
): string {
|
|
9
|
-
switch (name) {
|
|
10
|
-
<% for (const propName of names) { -%>
|
|
11
|
-
case <%= JSON.stringify(propName) %>:
|
|
12
|
-
// TODO: customize mapping for this entity. Example for skillProficiency:
|
|
13
|
-
// return JSON.stringify({ displayName: String(row["skill"] ?? ""), proficiency: String(row["proficiency"] ?? "") });
|
|
14
|
-
return value;
|
|
15
|
-
<% } -%>
|
|
16
|
-
default:
|
|
17
|
-
return value;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function transformPersonEntityCollection(
|
|
22
|
-
name: string,
|
|
23
|
-
row: Record<string, unknown>,
|
|
24
|
-
value: string[]
|
|
25
|
-
): string[] {
|
|
26
|
-
switch (name) {
|
|
27
|
-
<% for (const propName of names) { -%>
|
|
28
|
-
case <%= JSON.stringify(propName) %>:
|
|
29
|
-
// TODO: customize mapping for this collection entity. Example for skills:
|
|
30
|
-
// return [JSON.stringify({ displayName: String(row["skill"] ?? ""), proficiency: String(row["proficiency"] ?? "") })];
|
|
31
|
-
return value;
|
|
32
|
-
<% } -%>
|
|
33
|
-
default:
|
|
34
|
-
return value;
|
|
35
|
-
}
|
|
36
|
-
}
|