@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,30 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connector CLI for provisioning, ingestion, and deletion.
|
|
3
|
+
*/
|
|
1
4
|
import "dotenv/config";
|
|
2
5
|
|
|
3
6
|
import { Command } from "commander";
|
|
4
7
|
import { ClientSecretCredential } from "@azure/identity";
|
|
5
8
|
|
|
6
|
-
import type {
|
|
9
|
+
import type { <%= itemTypeName %> } from "./<%= schemaFolderName %>/model.js";
|
|
7
10
|
import {
|
|
8
11
|
contentCategory,
|
|
12
|
+
connectionName as defaultConnectionName,
|
|
9
13
|
connectionId as defaultConnectionId,
|
|
10
14
|
connectionDescription as defaultConnectionDescription,
|
|
11
15
|
profileSourceWebUrl as defaultProfileSourceWebUrl,
|
|
12
16
|
profileSourceDisplayName as defaultProfileSourceDisplayName,
|
|
13
17
|
profileSourcePriority as defaultProfileSourcePriority,
|
|
14
18
|
schemaPayload
|
|
15
|
-
} from "
|
|
16
|
-
import { getItemId, toExternalItem } from "
|
|
19
|
+
} from "./<%= schemaFolderName %>/index.js";
|
|
20
|
+
import { getItemId, toExternalItem } from "./<%= schemaFolderName %>/itemPayload.js";
|
|
17
21
|
import { CsvItemSource } from "./datasource/csvItemSource.js";
|
|
22
|
+
import { ConnectorCore } from "./core/connectorCore.js";
|
|
18
23
|
|
|
19
24
|
const GRAPH_BASE_URL = <%= JSON.stringify(graphBaseUrl) %>;
|
|
20
|
-
const PROFILE_SOURCE_URL_PREFIX = `${GRAPH_BASE_URL}/admin/people/profileSources`;
|
|
21
25
|
|
|
26
|
+
const useColor = !process.env.NO_COLOR;
|
|
27
|
+
const color = {
|
|
28
|
+
cyan: (value: string) => (useColor ? `\u001b[36m${value}\u001b[0m` : value),
|
|
29
|
+
green: (value: string) => (useColor ? `\u001b[32m${value}\u001b[0m` : value),
|
|
30
|
+
yellow: (value: string) => (useColor ? `\u001b[33m${value}\u001b[0m` : value),
|
|
31
|
+
dim: (value: string) => (useColor ? `\u001b[2m${value}\u001b[0m` : value),
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function printBanner(): void {
|
|
35
|
+
const title = color.cyan(`✨ 🥥 <%= itemTypeName %> connector CLI 🥥 ✨`);
|
|
36
|
+
const subtitle = color.dim("Provision • Ingest • Delete");
|
|
37
|
+
console.log(`${title}\n${subtitle}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
printBanner();
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Resolve a required environment variable or throw a friendly error.
|
|
44
|
+
*/
|
|
22
45
|
function requiredEnv(name: string): string {
|
|
23
46
|
const value = process.env[name];
|
|
24
47
|
if (!value) throw new Error(`Missing env var: ${name}`);
|
|
25
48
|
return value;
|
|
26
49
|
}
|
|
27
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Resolve the connection ID from env or schema defaults.
|
|
53
|
+
*/
|
|
28
54
|
function resolveConnectionId(): string {
|
|
29
55
|
const value = process.env.CONNECTION_ID ?? defaultConnectionId;
|
|
30
56
|
if (!value) throw new Error("Missing env var: CONNECTION_ID");
|
|
@@ -32,7 +58,9 @@ function resolveConnectionId(): string {
|
|
|
32
58
|
}
|
|
33
59
|
|
|
34
60
|
function resolveConnectionName(): string {
|
|
35
|
-
|
|
61
|
+
const value = process.env.CONNECTION_NAME ?? defaultConnectionName;
|
|
62
|
+
if (!value) throw new Error("Missing env var: CONNECTION_NAME");
|
|
63
|
+
return value;
|
|
36
64
|
}
|
|
37
65
|
|
|
38
66
|
function resolveConnectionDescription(): string {
|
|
@@ -53,7 +81,7 @@ function resolveProfileSourcePriority(): "first" | "last" {
|
|
|
53
81
|
if (value !== "first" && value !== "last") {
|
|
54
82
|
throw new Error("Invalid PROFILE_SOURCE_PRIORITY: expected 'first' or 'last'");
|
|
55
83
|
}
|
|
56
|
-
return value;
|
|
84
|
+
return value as "first" | "last";
|
|
57
85
|
}
|
|
58
86
|
|
|
59
87
|
async function getAccessToken(): Promise<string> {
|
|
@@ -67,173 +95,56 @@ async function getAccessToken(): Promise<string> {
|
|
|
67
95
|
return token.token;
|
|
68
96
|
}
|
|
69
97
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Build a reusable connector core instance.
|
|
100
|
+
*/
|
|
101
|
+
function buildConnectorCore(): ConnectorCore<<%= itemTypeName %>> {
|
|
102
|
+
const profileSource = <%- isPeopleConnector
|
|
103
|
+
? `{
|
|
104
|
+
webUrl: resolveProfileSourceWebUrl(),
|
|
105
|
+
displayName: process.env.PROFILE_SOURCE_DISPLAY_NAME ?? defaultProfileSourceDisplayName ?? requiredEnv("CONNECTION_NAME"),
|
|
106
|
+
priority: resolveProfileSourcePriority()
|
|
107
|
+
}`
|
|
108
|
+
: "undefined" %>;
|
|
109
|
+
|
|
110
|
+
return new ConnectorCore<<%= itemTypeName %>>({
|
|
111
|
+
graphBaseUrl: GRAPH_BASE_URL,
|
|
112
|
+
contentCategory,
|
|
113
|
+
schemaPayload,
|
|
114
|
+
getAccessToken,
|
|
115
|
+
getItemId,
|
|
116
|
+
toExternalItem,
|
|
117
|
+
profileSource
|
|
79
118
|
});
|
|
80
119
|
}
|
|
81
120
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (!res.ok) {
|
|
86
|
-
const text = await res.text();
|
|
87
|
-
throw new Error(`Failed to list profile property settings (HTTP ${res.status}): ${text}`);
|
|
88
|
-
}
|
|
89
|
-
const json = (await res.json()) as { value?: Array<{ id?: string; prioritizedSourceUrls?: string[] }> };
|
|
90
|
-
return (json.value ?? [])
|
|
91
|
-
.filter((entry) => typeof entry.id === "string")
|
|
92
|
-
.map((entry) => ({
|
|
93
|
-
id: entry.id as string,
|
|
94
|
-
prioritizedSourceUrls: Array.isArray(entry.prioritizedSourceUrls) ? entry.prioritizedSourceUrls : [],
|
|
95
|
-
}));
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async function registerProfileSource(connectionId: string): Promise<void> {
|
|
99
|
-
const webUrl = resolveProfileSourceWebUrl();
|
|
100
|
-
const displayName =
|
|
101
|
-
process.env.PROFILE_SOURCE_DISPLAY_NAME ?? defaultProfileSourceDisplayName ?? requiredEnv("CONNECTION_NAME");
|
|
102
|
-
const priority = resolveProfileSourcePriority();
|
|
103
|
-
|
|
104
|
-
const payload: any = {
|
|
105
|
-
sourceId: connectionId,
|
|
106
|
-
displayName,
|
|
107
|
-
webUrl
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const create = await graphRequest("POST", `${GRAPH_BASE_URL}/admin/people/profileSources`, payload);
|
|
111
|
-
if (!create.ok && create.status !== 409) {
|
|
112
|
-
const text = await create.text();
|
|
113
|
-
throw new Error(`Failed to register profile source (HTTP ${create.status}): ${text}`);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const sourceUrl = `${PROFILE_SOURCE_URL_PREFIX}(sourceId='${connectionId}')`;
|
|
117
|
-
const settings = await listProfilePropertySettings();
|
|
118
|
-
for (const setting of settings) {
|
|
119
|
-
const existing = setting.prioritizedSourceUrls.filter((value) => value !== sourceUrl);
|
|
120
|
-
const updated = priority === "first" ? [sourceUrl, ...existing] : [...existing, sourceUrl];
|
|
121
|
-
const res = await graphRequest(
|
|
122
|
-
"PATCH",
|
|
123
|
-
`${GRAPH_BASE_URL}/admin/people/profilePropertySettings/${setting.id}`,
|
|
124
|
-
{
|
|
125
|
-
"@odata.type": "#microsoft.graph.profilePropertySetting",
|
|
126
|
-
prioritizedSourceUrls: updated
|
|
127
|
-
}
|
|
128
|
-
);
|
|
129
|
-
if (!res.ok) {
|
|
130
|
-
const text = await res.text();
|
|
131
|
-
throw new Error(`Failed to update profile property setting ${setting.id} (HTTP ${res.status}): ${text}`);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
async function unregisterProfileSource(connectionId: string): Promise<void> {
|
|
137
|
-
const sourceUrl = `${PROFILE_SOURCE_URL_PREFIX}(sourceId='${connectionId}')`;
|
|
138
|
-
const settings = await listProfilePropertySettings();
|
|
139
|
-
for (const setting of settings) {
|
|
140
|
-
const updated = setting.prioritizedSourceUrls.filter((value) => value !== sourceUrl);
|
|
141
|
-
const res = await graphRequest(
|
|
142
|
-
"PATCH",
|
|
143
|
-
`${GRAPH_BASE_URL}/admin/people/profilePropertySettings/${setting.id}`,
|
|
144
|
-
{
|
|
145
|
-
"@odata.type": "#microsoft.graph.profilePropertySetting",
|
|
146
|
-
prioritizedSourceUrls: updated
|
|
147
|
-
}
|
|
148
|
-
);
|
|
149
|
-
if (!res.ok) {
|
|
150
|
-
const text = await res.text();
|
|
151
|
-
throw new Error(`Failed to update profile property setting ${setting.id} (HTTP ${res.status}): ${text}`);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const res = await graphRequest(
|
|
156
|
-
"DELETE",
|
|
157
|
-
`${PROFILE_SOURCE_URL_PREFIX}(sourceId='${connectionId}')`
|
|
158
|
-
);
|
|
159
|
-
if (!res.ok && res.status !== 404) {
|
|
160
|
-
const text = await res.text();
|
|
161
|
-
throw new Error(`Failed to delete profile source (HTTP ${res.status}): ${text}`);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
<% } -%>
|
|
165
|
-
|
|
166
|
-
async function ensureConnection(connectionId: string): Promise<void> {
|
|
167
|
-
const name = resolveConnectionName();
|
|
168
|
-
const description = resolveConnectionDescription();
|
|
169
|
-
|
|
170
|
-
const payload: any = {
|
|
171
|
-
id: connectionId,
|
|
172
|
-
name,
|
|
173
|
-
description
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
if (contentCategory) payload.contentCategory = contentCategory;
|
|
177
|
-
|
|
178
|
-
const createUrl = `${GRAPH_BASE_URL}/external/connections`;
|
|
179
|
-
const create = await graphRequest("POST", createUrl, payload);
|
|
180
|
-
if (!create.ok) {
|
|
181
|
-
if (create.status !== 409) {
|
|
182
|
-
const text = await create.text();
|
|
183
|
-
throw new Error(`Failed to create connection (HTTP ${create.status}): ${text}`);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
async function patchSchema(connectionId: string): Promise<void> {
|
|
189
|
-
const schemaUrl = `${GRAPH_BASE_URL}/external/connections/${connectionId}/schema`;
|
|
190
|
-
const res = await graphRequest("PATCH", schemaUrl, schemaPayload);
|
|
191
|
-
if (!res.ok) {
|
|
192
|
-
const text = await res.text();
|
|
193
|
-
throw new Error(`Failed to patch schema (HTTP ${res.status}): ${text}`);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Provision connection + schema and (optionally) profile source.
|
|
123
|
+
*/
|
|
197
124
|
async function provision(): Promise<void> {
|
|
198
125
|
const connectionId = resolveConnectionId();
|
|
199
|
-
|
|
200
|
-
await
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
126
|
+
const core = buildConnectorCore();
|
|
127
|
+
await core.provision({
|
|
128
|
+
connectionId,
|
|
129
|
+
connectionName: resolveConnectionName(),
|
|
130
|
+
connectionDescription: resolveConnectionDescription()
|
|
131
|
+
});
|
|
204
132
|
console.log("ok: provisioned");
|
|
205
133
|
}
|
|
206
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Delete the external connection.
|
|
137
|
+
*/
|
|
207
138
|
async function deleteConnection(): Promise<void> {
|
|
208
139
|
const connectionId = resolveConnectionId();
|
|
209
|
-
|
|
210
|
-
await
|
|
211
|
-
<% } -%>
|
|
212
|
-
const res = await graphRequest("DELETE", `${GRAPH_BASE_URL}/external/connections/${connectionId}`);
|
|
213
|
-
if (!res.ok && res.status !== 404) {
|
|
214
|
-
const text = await res.text();
|
|
215
|
-
throw new Error(`Failed to delete connection (HTTP ${res.status}): ${text}`);
|
|
216
|
-
}
|
|
140
|
+
const core = buildConnectorCore();
|
|
141
|
+
await core.deleteConnection(connectionId);
|
|
217
142
|
console.log("ok: deleted");
|
|
218
143
|
}
|
|
219
144
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const payload = toExternalItem(item);
|
|
225
|
-
if (verbose) {
|
|
226
|
-
console.log("verbose: PUT", url);
|
|
227
|
-
console.log("verbose: payload", JSON.stringify(payload, null, 2));
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const res = await graphRequest("PUT", url, payload);
|
|
231
|
-
if (!res.ok) {
|
|
232
|
-
const text = await res.text();
|
|
233
|
-
throw new Error(`Failed to ingest item '${itemId}' (HTTP ${res.status}): ${text}`);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
145
|
+
/**
|
|
146
|
+
* Ingest items from the configured datasource.
|
|
147
|
+
*/
|
|
237
148
|
async function ingest(options: {
|
|
238
149
|
csvPath?: string;
|
|
239
150
|
dryRun?: boolean;
|
|
@@ -243,18 +154,15 @@ async function ingest(options: {
|
|
|
243
154
|
const connectionId = options.dryRun ? "dry-run" : resolveConnectionId();
|
|
244
155
|
// Swap this for any ItemSource implementation (API, DB, queue, etc.).
|
|
245
156
|
const source = new CsvItemSource(options.csvPath ?? process.env.CSV_PATH ?? "data.csv");
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
console.log("ok: ingested " + count + " item(s)");
|
|
157
|
+
const core = buildConnectorCore();
|
|
158
|
+
await core.ingest({
|
|
159
|
+
source,
|
|
160
|
+
connectionId,
|
|
161
|
+
dryRun: options.dryRun,
|
|
162
|
+
limit: options.limit,
|
|
163
|
+
verbose: options.verbose,
|
|
164
|
+
toExternalItem
|
|
165
|
+
});
|
|
258
166
|
}
|
|
259
167
|
|
|
260
168
|
const program = new Command();
|
|
@@ -285,7 +193,11 @@ program
|
|
|
285
193
|
program
|
|
286
194
|
.command("register-profile-source")
|
|
287
195
|
.description("Register the connection as a profile source (people connectors)")
|
|
288
|
-
.action(() =>
|
|
196
|
+
.action(async () => {
|
|
197
|
+
const connectionId = resolveConnectionId();
|
|
198
|
+
const core = buildConnectorCore();
|
|
199
|
+
await core.registerProfileSource(connectionId);
|
|
200
|
+
});
|
|
289
201
|
<% } -%>
|
|
290
202
|
|
|
291
203
|
program
|