@mustafaaksoy41/sharepoint-kit 0.1.2
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 +293 -0
- package/dist/bin/sp-generate-types.js +784 -0
- package/dist/bin/sp-generate-types.js.map +1 -0
- package/dist/chunk-2FU6XS6S.cjs +142 -0
- package/dist/chunk-2FU6XS6S.cjs.map +1 -0
- package/dist/chunk-MLY32NZB.js +131 -0
- package/dist/chunk-MLY32NZB.js.map +1 -0
- package/dist/chunk-V6K5IFVV.cjs +253 -0
- package/dist/chunk-V6K5IFVV.cjs.map +1 -0
- package/dist/chunk-VOGWZXJY.js +246 -0
- package/dist/chunk-VOGWZXJY.js.map +1 -0
- package/dist/cli/index.cjs +516 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +117 -0
- package/dist/cli/index.d.ts +117 -0
- package/dist/cli/index.js +505 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/components/index.cjs +509 -0
- package/dist/components/index.cjs.map +1 -0
- package/dist/components/index.d.cts +118 -0
- package/dist/components/index.d.ts +118 -0
- package/dist/components/index.js +494 -0
- package/dist/components/index.js.map +1 -0
- package/dist/config-loader-Nbidwviq.d.cts +33 -0
- package/dist/config-loader-Nbidwviq.d.ts +33 -0
- package/dist/hooks/index.cjs +45 -0
- package/dist/hooks/index.cjs.map +1 -0
- package/dist/hooks/index.d.cts +51 -0
- package/dist/hooks/index.d.ts +51 -0
- package/dist/hooks/index.js +24 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/index.cjs +32 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +32 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/sp-client-A9dM9oYp.d.ts +31 -0
- package/dist/sp-client-DTChApOB.d.cts +31 -0
- package/dist/types-Dk0jbejG.d.cts +108 -0
- package/dist/types-Dk0jbejG.d.ts +108 -0
- package/package.json +123 -0
|
@@ -0,0 +1,784 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
#!/usr/bin/env node
|
|
3
|
+
|
|
4
|
+
// bin/sp-generate-types.ts
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
|
|
7
|
+
// src/cli/generate-types.ts
|
|
8
|
+
import { writeFileSync as writeFileSync2, mkdirSync } from "fs";
|
|
9
|
+
import { dirname, resolve as resolve2 } from "path";
|
|
10
|
+
import { ConfidentialClientApplication } from "@azure/msal-node";
|
|
11
|
+
|
|
12
|
+
// src/cli/config-loader.ts
|
|
13
|
+
import { readFileSync, existsSync } from "fs";
|
|
14
|
+
import { resolve, extname } from "path";
|
|
15
|
+
async function loadConfig(configPath) {
|
|
16
|
+
const absolutePath = resolve(process.cwd(), configPath);
|
|
17
|
+
if (!existsSync(absolutePath)) {
|
|
18
|
+
throw new Error(`Config file not found: ${absolutePath}`);
|
|
19
|
+
}
|
|
20
|
+
const ext = extname(absolutePath);
|
|
21
|
+
let config;
|
|
22
|
+
if (ext === ".json") {
|
|
23
|
+
const content = readFileSync(absolutePath, "utf-8");
|
|
24
|
+
config = JSON.parse(content);
|
|
25
|
+
} else if (ext === ".ts" || ext === ".js" || ext === ".mjs") {
|
|
26
|
+
const module = await import(absolutePath);
|
|
27
|
+
config = module.default ?? module;
|
|
28
|
+
} else {
|
|
29
|
+
throw new Error(`Unsupported config file format: ${ext}. Use .json, .ts, .js, or .mjs`);
|
|
30
|
+
}
|
|
31
|
+
validateConfig(config);
|
|
32
|
+
return config;
|
|
33
|
+
}
|
|
34
|
+
function validateConfig(config) {
|
|
35
|
+
if (!config || typeof config !== "object") {
|
|
36
|
+
throw new Error("Config must be an object");
|
|
37
|
+
}
|
|
38
|
+
const c = config;
|
|
39
|
+
if (!c.siteId || typeof c.siteId !== "string") {
|
|
40
|
+
throw new Error('Config must have a "siteId" string property');
|
|
41
|
+
}
|
|
42
|
+
if (!Array.isArray(c.contentTypes) || c.contentTypes.length === 0) {
|
|
43
|
+
throw new Error('Config must have a non-empty "contentTypes" array');
|
|
44
|
+
}
|
|
45
|
+
for (const [index, ct] of c.contentTypes.entries()) {
|
|
46
|
+
if (!ct || typeof ct !== "object") {
|
|
47
|
+
throw new Error(`contentTypes[${index}] must be an object`);
|
|
48
|
+
}
|
|
49
|
+
const ctObj = ct;
|
|
50
|
+
if (!ctObj.contentTypeName || typeof ctObj.contentTypeName !== "string") {
|
|
51
|
+
throw new Error(`contentTypes[${index}] must have a "contentTypeName" string property`);
|
|
52
|
+
}
|
|
53
|
+
if (!ctObj.outputType || typeof ctObj.outputType !== "string") {
|
|
54
|
+
throw new Error(`contentTypes[${index}] must have an "outputType" string property`);
|
|
55
|
+
}
|
|
56
|
+
if (ctObj.listId !== void 0 && typeof ctObj.listId !== "string") {
|
|
57
|
+
throw new Error(`contentTypes[${index}].listId must be a string`);
|
|
58
|
+
}
|
|
59
|
+
if (ctObj.listName !== void 0 && typeof ctObj.listName !== "string") {
|
|
60
|
+
throw new Error(`contentTypes[${index}].listName must be a string`);
|
|
61
|
+
}
|
|
62
|
+
const validStrategies = ["interactive", "first", "error", "all"];
|
|
63
|
+
if (ctObj.strategy !== void 0 && !validStrategies.includes(ctObj.strategy)) {
|
|
64
|
+
throw new Error(`contentTypes[${index}].strategy must be one of: ${validStrategies.join(", ")}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (c.defaultStrategy !== void 0) {
|
|
68
|
+
const validStrategies = ["interactive", "first", "error", "all"];
|
|
69
|
+
if (!validStrategies.includes(c.defaultStrategy)) {
|
|
70
|
+
throw new Error(`defaultStrategy must be one of: ${validStrategies.join(", ")}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/cli/cache-manager.ts
|
|
76
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2 } from "fs";
|
|
77
|
+
import { join } from "path";
|
|
78
|
+
var CACHE_FILE = ".sharepoint-cache.json";
|
|
79
|
+
var CacheManager = class {
|
|
80
|
+
constructor(outputDir = "./") {
|
|
81
|
+
this.cachePath = join(outputDir, CACHE_FILE);
|
|
82
|
+
}
|
|
83
|
+
load() {
|
|
84
|
+
if (!existsSync2(this.cachePath)) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const content = readFileSync2(this.cachePath, "utf-8");
|
|
89
|
+
const data = JSON.parse(content);
|
|
90
|
+
if (!data || typeof data !== "object") return null;
|
|
91
|
+
return data;
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
save(data) {
|
|
97
|
+
writeFileSync(this.cachePath, JSON.stringify(data, null, 2), "utf-8");
|
|
98
|
+
}
|
|
99
|
+
getResolution(contentTypeName) {
|
|
100
|
+
const cache = this.load();
|
|
101
|
+
if (!cache) return null;
|
|
102
|
+
return cache.resolutions[contentTypeName] ?? null;
|
|
103
|
+
}
|
|
104
|
+
saveResolution(siteId, contentTypeName, listId, listName) {
|
|
105
|
+
const cache = this.load() ?? {
|
|
106
|
+
siteId,
|
|
107
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
108
|
+
lists: [],
|
|
109
|
+
resolutions: {}
|
|
110
|
+
};
|
|
111
|
+
cache.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
112
|
+
cache.siteId = siteId;
|
|
113
|
+
cache.resolutions[contentTypeName] = {
|
|
114
|
+
listId,
|
|
115
|
+
listName,
|
|
116
|
+
resolvedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
117
|
+
};
|
|
118
|
+
this.save(cache);
|
|
119
|
+
}
|
|
120
|
+
saveLists(siteId, lists) {
|
|
121
|
+
const cache = this.load() ?? {
|
|
122
|
+
siteId,
|
|
123
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
124
|
+
lists: [],
|
|
125
|
+
resolutions: {}
|
|
126
|
+
};
|
|
127
|
+
cache.siteId = siteId;
|
|
128
|
+
cache.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
129
|
+
cache.lists = lists;
|
|
130
|
+
this.save(cache);
|
|
131
|
+
}
|
|
132
|
+
clear() {
|
|
133
|
+
if (existsSync2(this.cachePath)) {
|
|
134
|
+
writeFileSync(this.cachePath, "{}", "utf-8");
|
|
135
|
+
console.log("Cache cleared.");
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
getCachePath() {
|
|
139
|
+
return this.cachePath;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// src/cli/interactive-prompt.ts
|
|
144
|
+
import prompts from "prompts";
|
|
145
|
+
async function promptListSelection(contentTypeName, lists, strategy = "interactive") {
|
|
146
|
+
if (strategy === "first") {
|
|
147
|
+
console.log(` Using first match: "${lists[0].displayName}" (${lists[0].id})`);
|
|
148
|
+
return [{ listId: lists[0].id, listName: lists[0].displayName }];
|
|
149
|
+
}
|
|
150
|
+
if (strategy === "error") {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`Content type "${contentTypeName}" found in ${lists.length} lists. Please specify listId or listName in config, or use --strategy=all`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
if (strategy === "all") {
|
|
156
|
+
console.log(` Generating types for ALL ${lists.length} lists`);
|
|
157
|
+
return lists.map((list) => ({
|
|
158
|
+
listId: list.id,
|
|
159
|
+
listName: list.displayName
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
console.log(`
|
|
163
|
+
Content type "${contentTypeName}" found in ${lists.length} lists:
|
|
164
|
+
`);
|
|
165
|
+
const choices = [
|
|
166
|
+
...lists.map((list, index) => ({
|
|
167
|
+
title: `${index + 1}. ${list.displayName} (${list.id})`,
|
|
168
|
+
value: list.id,
|
|
169
|
+
description: `List ID: ${list.id}`
|
|
170
|
+
})),
|
|
171
|
+
{
|
|
172
|
+
title: `Generate types for ALL ${lists.length} lists`,
|
|
173
|
+
value: "__all__",
|
|
174
|
+
description: lists.map((l) => l.displayName).join(", ")
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
title: "Skip this content type",
|
|
178
|
+
value: "__skip__",
|
|
179
|
+
description: "Do not generate types for this content type"
|
|
180
|
+
}
|
|
181
|
+
];
|
|
182
|
+
const response = await prompts({
|
|
183
|
+
type: "select",
|
|
184
|
+
name: "selection",
|
|
185
|
+
message: `Which list(s) should be used for "${contentTypeName}"?`,
|
|
186
|
+
choices,
|
|
187
|
+
initial: 0
|
|
188
|
+
});
|
|
189
|
+
if (!response.selection || response.selection === "__skip__") {
|
|
190
|
+
console.log(` Skipped "${contentTypeName}"
|
|
191
|
+
`);
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
if (response.selection === "__all__") {
|
|
195
|
+
console.log(` Generating types for all lists
|
|
196
|
+
`);
|
|
197
|
+
return lists.map((list) => ({
|
|
198
|
+
listId: list.id,
|
|
199
|
+
listName: list.displayName
|
|
200
|
+
}));
|
|
201
|
+
}
|
|
202
|
+
const selectedList = lists.find((l) => l.id === response.selection);
|
|
203
|
+
if (!selectedList) {
|
|
204
|
+
throw new Error("Invalid selection");
|
|
205
|
+
}
|
|
206
|
+
console.log(` Selected: ${selectedList.displayName}
|
|
207
|
+
`);
|
|
208
|
+
return [{ listId: response.selection, listName: selectedList.displayName }];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// src/cli/list-resolver.ts
|
|
212
|
+
async function resolveContentType(config, siteId, graphClient, cacheManager, globalStrategy, nonInteractive) {
|
|
213
|
+
const strategy = config.strategy ?? globalStrategy;
|
|
214
|
+
if (config.listId) {
|
|
215
|
+
const list = await graphClient.getList({ listId: config.listId });
|
|
216
|
+
console.log(` Found list "${list.displayName}" (${list.id})`);
|
|
217
|
+
return [{
|
|
218
|
+
contentTypeName: config.contentTypeName,
|
|
219
|
+
outputType: config.outputType,
|
|
220
|
+
listId: config.listId,
|
|
221
|
+
listName: list.displayName
|
|
222
|
+
}];
|
|
223
|
+
}
|
|
224
|
+
const cached = cacheManager.getResolution(config.contentTypeName);
|
|
225
|
+
if (cached) {
|
|
226
|
+
console.log(` Using cached resolution for "${config.contentTypeName}": ${cached.listName}`);
|
|
227
|
+
return [{
|
|
228
|
+
contentTypeName: config.contentTypeName,
|
|
229
|
+
outputType: config.outputType,
|
|
230
|
+
listId: cached.listId,
|
|
231
|
+
listName: cached.listName
|
|
232
|
+
}];
|
|
233
|
+
}
|
|
234
|
+
const lists = await graphClient.getLists();
|
|
235
|
+
if (config.listName) {
|
|
236
|
+
const list = lists.find(
|
|
237
|
+
(l) => l.displayName === config.listName || l.name === config.listName
|
|
238
|
+
);
|
|
239
|
+
if (!list) {
|
|
240
|
+
throw new Error(`List "${config.listName}" not found in site "${siteId}"`);
|
|
241
|
+
}
|
|
242
|
+
console.log(` Found list "${list.displayName}" (${list.id})`);
|
|
243
|
+
cacheManager.saveResolution(siteId, config.contentTypeName, list.id, list.displayName);
|
|
244
|
+
return [{
|
|
245
|
+
contentTypeName: config.contentTypeName,
|
|
246
|
+
outputType: config.outputType,
|
|
247
|
+
listId: list.id,
|
|
248
|
+
listName: list.displayName
|
|
249
|
+
}];
|
|
250
|
+
}
|
|
251
|
+
console.log(` Scanning lists for content type "${config.contentTypeName}"...`);
|
|
252
|
+
const matchingLists = [];
|
|
253
|
+
for (const list of lists) {
|
|
254
|
+
try {
|
|
255
|
+
const contentTypes = await graphClient.getListContentTypes({ listId: list.id });
|
|
256
|
+
if (contentTypes.some((ct) => ct.name === config.contentTypeName)) {
|
|
257
|
+
matchingLists.push({
|
|
258
|
+
id: list.id,
|
|
259
|
+
displayName: list.displayName,
|
|
260
|
+
name: list.name
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
} catch {
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (matchingLists.length === 0) {
|
|
267
|
+
throw new Error(
|
|
268
|
+
`Content type "${config.contentTypeName}" not found in any list of site "${siteId}"`
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
if (matchingLists.length === 1) {
|
|
272
|
+
const list = matchingLists[0];
|
|
273
|
+
console.log(` Found content type "${config.contentTypeName}" in list "${list.displayName}"`);
|
|
274
|
+
cacheManager.saveResolution(siteId, config.contentTypeName, list.id, list.displayName);
|
|
275
|
+
return [{
|
|
276
|
+
contentTypeName: config.contentTypeName,
|
|
277
|
+
outputType: config.outputType,
|
|
278
|
+
listId: list.id,
|
|
279
|
+
listName: list.displayName
|
|
280
|
+
}];
|
|
281
|
+
}
|
|
282
|
+
const effectiveStrategy = nonInteractive && strategy === "interactive" ? "first" : strategy;
|
|
283
|
+
const selectedLists = await promptListSelection(
|
|
284
|
+
config.contentTypeName,
|
|
285
|
+
matchingLists,
|
|
286
|
+
effectiveStrategy
|
|
287
|
+
);
|
|
288
|
+
if (selectedLists.length === 0) {
|
|
289
|
+
return [];
|
|
290
|
+
}
|
|
291
|
+
if (selectedLists.length === 1) {
|
|
292
|
+
cacheManager.saveResolution(
|
|
293
|
+
siteId,
|
|
294
|
+
config.contentTypeName,
|
|
295
|
+
selectedLists[0].listId,
|
|
296
|
+
selectedLists[0].listName
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
if (selectedLists.length > 1) {
|
|
300
|
+
return selectedLists.map((list) => ({
|
|
301
|
+
contentTypeName: config.contentTypeName,
|
|
302
|
+
outputType: `${config.outputType}_${list.listName.replace(/\s+/g, "")}`,
|
|
303
|
+
listId: list.listId,
|
|
304
|
+
listName: list.listName
|
|
305
|
+
}));
|
|
306
|
+
}
|
|
307
|
+
return selectedLists.map((list) => ({
|
|
308
|
+
contentTypeName: config.contentTypeName,
|
|
309
|
+
outputType: config.outputType,
|
|
310
|
+
listId: list.listId,
|
|
311
|
+
listName: list.listName
|
|
312
|
+
}));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// src/cli/templates.ts
|
|
316
|
+
function generateFileHeader() {
|
|
317
|
+
return [
|
|
318
|
+
"// Auto-generated by @mustafaaksoy41/sharepoint-kit CLI",
|
|
319
|
+
"// Do not edit manually. Run: npx sp-generate-types --config sharepoint.config.ts",
|
|
320
|
+
""
|
|
321
|
+
].join("\n");
|
|
322
|
+
}
|
|
323
|
+
function generateInterface(name, fields) {
|
|
324
|
+
const lines = [];
|
|
325
|
+
lines.push(`export interface ${name} {`);
|
|
326
|
+
for (const field of fields) {
|
|
327
|
+
if (field.description) {
|
|
328
|
+
lines.push(` /** ${field.description} */`);
|
|
329
|
+
}
|
|
330
|
+
const optional = field.required ? "" : "?";
|
|
331
|
+
lines.push(` ${field.name}${optional}: ${field.tsType};`);
|
|
332
|
+
}
|
|
333
|
+
lines.push("}");
|
|
334
|
+
return lines.join("\n");
|
|
335
|
+
}
|
|
336
|
+
function mapSharePointTypeToTs(spType) {
|
|
337
|
+
switch (spType) {
|
|
338
|
+
case "Text":
|
|
339
|
+
case "Note":
|
|
340
|
+
case "Choice":
|
|
341
|
+
return "string";
|
|
342
|
+
case "MultiChoice":
|
|
343
|
+
return "string[]";
|
|
344
|
+
case "Number":
|
|
345
|
+
case "Currency":
|
|
346
|
+
return "number";
|
|
347
|
+
case "Boolean":
|
|
348
|
+
return "boolean";
|
|
349
|
+
case "DateTime":
|
|
350
|
+
return "string";
|
|
351
|
+
case "Lookup":
|
|
352
|
+
return "{ id: number; value: string }";
|
|
353
|
+
case "User":
|
|
354
|
+
return "{ id: number; email: string; displayName: string }";
|
|
355
|
+
case "URL":
|
|
356
|
+
return "{ url: string; description: string }";
|
|
357
|
+
case "Taxonomy":
|
|
358
|
+
return "{ termGuid: string; label: string }";
|
|
359
|
+
case "Calculated":
|
|
360
|
+
return "string | number";
|
|
361
|
+
default:
|
|
362
|
+
return "unknown";
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function sanitizeInterfaceName(name) {
|
|
366
|
+
return name.replace(/[^a-zA-Z0-9_]/g, "").replace(/^(\d)/, "_$1");
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// src/cli/type-generator.ts
|
|
370
|
+
async function generateTypeScript(inputs, graphClient, options = {}) {
|
|
371
|
+
const { fieldNameMapping = {} } = options;
|
|
372
|
+
const interfaces = [generateFileHeader()];
|
|
373
|
+
for (const input of inputs) {
|
|
374
|
+
console.log(` Generating interface "${input.outputType}" from list "${input.listName}"...`);
|
|
375
|
+
const contentTypes = await graphClient.getListContentTypes({ listId: input.listId });
|
|
376
|
+
const contentType = contentTypes.find((ct) => ct.name === input.contentTypeName);
|
|
377
|
+
let columns;
|
|
378
|
+
if (contentType) {
|
|
379
|
+
columns = await graphClient.getColumns({ contentTypeId: contentType.id });
|
|
380
|
+
} else {
|
|
381
|
+
console.log(` Content type not found by ID, falling back to list columns`);
|
|
382
|
+
columns = await graphClient.getListColumns({ listId: input.listId });
|
|
383
|
+
}
|
|
384
|
+
const writableColumns = columns.filter((col) => !col.readOnly && !isSystemColumn(col.name));
|
|
385
|
+
const fields = writableColumns.map((col) => {
|
|
386
|
+
const mappedName = fieldNameMapping[col.name] ?? col.name;
|
|
387
|
+
const tsType = mapSharePointTypeToTs(col.type);
|
|
388
|
+
return {
|
|
389
|
+
name: mappedName,
|
|
390
|
+
tsType,
|
|
391
|
+
required: col.required,
|
|
392
|
+
description: col.displayName !== col.name ? col.displayName : void 0
|
|
393
|
+
};
|
|
394
|
+
});
|
|
395
|
+
const interfaceName = sanitizeInterfaceName(input.outputType);
|
|
396
|
+
interfaces.push(generateInterface(interfaceName, fields));
|
|
397
|
+
}
|
|
398
|
+
return interfaces.join("\n\n") + "\n";
|
|
399
|
+
}
|
|
400
|
+
function isSystemColumn(name) {
|
|
401
|
+
const systemColumns = /* @__PURE__ */ new Set([
|
|
402
|
+
"ContentType",
|
|
403
|
+
"Modified",
|
|
404
|
+
"Created",
|
|
405
|
+
"Author",
|
|
406
|
+
"Editor",
|
|
407
|
+
"_ModerationComments",
|
|
408
|
+
"_ModerationStatus",
|
|
409
|
+
"FileSystemObjectType",
|
|
410
|
+
"ServerRedirectedEmbedUri",
|
|
411
|
+
"ServerRedirectedEmbedUrl",
|
|
412
|
+
"ID",
|
|
413
|
+
"ContentTypeId",
|
|
414
|
+
"Attachments",
|
|
415
|
+
"GUID",
|
|
416
|
+
"OData__UIVersionString",
|
|
417
|
+
"ComplianceAssetId"
|
|
418
|
+
]);
|
|
419
|
+
return systemColumns.has(name);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// src/client/errors.ts
|
|
423
|
+
var SpError = class extends Error {
|
|
424
|
+
constructor(message, status, code, requestId = "") {
|
|
425
|
+
super(message);
|
|
426
|
+
this.name = "SpError";
|
|
427
|
+
this.status = status;
|
|
428
|
+
this.code = code;
|
|
429
|
+
this.requestId = requestId;
|
|
430
|
+
}
|
|
431
|
+
toJSON() {
|
|
432
|
+
return {
|
|
433
|
+
name: this.name,
|
|
434
|
+
message: this.message,
|
|
435
|
+
status: this.status,
|
|
436
|
+
code: this.code,
|
|
437
|
+
requestId: this.requestId
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
var SpAuthError = class extends SpError {
|
|
442
|
+
constructor(message, status = 401, requestId = "") {
|
|
443
|
+
super(message, status, "AuthenticationError", requestId);
|
|
444
|
+
this.name = "SpAuthError";
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
var SpNotFoundError = class extends SpError {
|
|
448
|
+
constructor(message, requestId = "") {
|
|
449
|
+
super(message, 404, "ItemNotFound", requestId);
|
|
450
|
+
this.name = "SpNotFoundError";
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
var SpThrottleError = class extends SpError {
|
|
454
|
+
constructor(message, retryAfter = 0, requestId = "") {
|
|
455
|
+
super(message, 429, "TooManyRequests", requestId);
|
|
456
|
+
this.name = "SpThrottleError";
|
|
457
|
+
this.retryAfter = retryAfter;
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
var SpValidationError = class extends SpError {
|
|
461
|
+
constructor(message, fieldErrors = {}, requestId = "") {
|
|
462
|
+
super(message, 400, "ValidationError", requestId);
|
|
463
|
+
this.name = "SpValidationError";
|
|
464
|
+
this.fieldErrors = fieldErrors;
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
function parseGraphError(status, body, retryAfterHeader) {
|
|
468
|
+
const { error } = body;
|
|
469
|
+
const requestId = error.innerError?.["request-id"] ?? "";
|
|
470
|
+
const message = error.message || "Unknown SharePoint error";
|
|
471
|
+
const code = error.code || "UnknownError";
|
|
472
|
+
if (status === 401 || status === 403) {
|
|
473
|
+
return new SpAuthError(message, status, requestId);
|
|
474
|
+
}
|
|
475
|
+
if (status === 404) {
|
|
476
|
+
return new SpNotFoundError(message, requestId);
|
|
477
|
+
}
|
|
478
|
+
if (status === 429) {
|
|
479
|
+
const retryAfter = retryAfterHeader ? parseInt(retryAfterHeader, 10) : 0;
|
|
480
|
+
return new SpThrottleError(message, retryAfter, requestId);
|
|
481
|
+
}
|
|
482
|
+
if (status === 400) {
|
|
483
|
+
return new SpValidationError(message, {}, requestId);
|
|
484
|
+
}
|
|
485
|
+
return new SpError(message, status, code, requestId);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// src/client/utils.ts
|
|
489
|
+
var GRAPH_BASE = "https://graph.microsoft.com/v1.0";
|
|
490
|
+
function buildGraphUrl(siteId, ...segments) {
|
|
491
|
+
const path = segments.filter(Boolean).join("/");
|
|
492
|
+
return `${GRAPH_BASE}/sites/${siteId}/${path}`;
|
|
493
|
+
}
|
|
494
|
+
function buildFilterQuery(contentTypeName, additionalFilter) {
|
|
495
|
+
const parts = [];
|
|
496
|
+
if (contentTypeName) {
|
|
497
|
+
parts.push(`fields/ContentType/Name eq '${contentTypeName}'`);
|
|
498
|
+
}
|
|
499
|
+
if (additionalFilter) {
|
|
500
|
+
parts.push(additionalFilter);
|
|
501
|
+
}
|
|
502
|
+
return parts.join(" and ");
|
|
503
|
+
}
|
|
504
|
+
function sleep(ms) {
|
|
505
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
506
|
+
}
|
|
507
|
+
function calculateBackoff(attempt, baseDelay = 1e3, maxDelay = 3e4) {
|
|
508
|
+
const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
|
|
509
|
+
const jitter = delay * 0.1 * Math.random();
|
|
510
|
+
return delay + jitter;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// src/client/sp-client.ts
|
|
514
|
+
var DEFAULT_RETRY = {
|
|
515
|
+
maxRetries: 3,
|
|
516
|
+
baseDelay: 1e3,
|
|
517
|
+
maxDelay: 3e4
|
|
518
|
+
};
|
|
519
|
+
function createSpClient(config) {
|
|
520
|
+
const { siteId, getAccessToken } = config;
|
|
521
|
+
const retryOpts = { ...DEFAULT_RETRY, ...config.retryOptions };
|
|
522
|
+
async function request(url, options = {}) {
|
|
523
|
+
let lastError = null;
|
|
524
|
+
for (let attempt = 0; attempt <= retryOpts.maxRetries; attempt++) {
|
|
525
|
+
const token = await getAccessToken();
|
|
526
|
+
const response = await fetch(url, {
|
|
527
|
+
...options,
|
|
528
|
+
headers: {
|
|
529
|
+
Authorization: `Bearer ${token}`,
|
|
530
|
+
"Content-Type": "application/json",
|
|
531
|
+
...options.headers
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
if (response.ok) {
|
|
535
|
+
if (response.status === 204) {
|
|
536
|
+
return void 0;
|
|
537
|
+
}
|
|
538
|
+
return await response.json();
|
|
539
|
+
}
|
|
540
|
+
const body = await response.json().catch(() => ({
|
|
541
|
+
error: { code: "UnknownError", message: response.statusText }
|
|
542
|
+
}));
|
|
543
|
+
const error = parseGraphError(
|
|
544
|
+
response.status,
|
|
545
|
+
body,
|
|
546
|
+
response.headers.get("Retry-After")
|
|
547
|
+
);
|
|
548
|
+
if (error instanceof SpThrottleError && attempt < retryOpts.maxRetries) {
|
|
549
|
+
const waitTime = error.retryAfter ? error.retryAfter * 1e3 : calculateBackoff(attempt, retryOpts.baseDelay, retryOpts.maxDelay);
|
|
550
|
+
await sleep(waitTime);
|
|
551
|
+
lastError = error;
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
throw error;
|
|
555
|
+
}
|
|
556
|
+
throw lastError ?? new Error("Max retries exceeded");
|
|
557
|
+
}
|
|
558
|
+
return {
|
|
559
|
+
async getListItems(options) {
|
|
560
|
+
const { listId, contentTypeName, filter, select, expand, orderBy, top, skip } = options;
|
|
561
|
+
const params = new URLSearchParams();
|
|
562
|
+
const filterQuery = buildFilterQuery(contentTypeName, filter);
|
|
563
|
+
if (filterQuery) params.set("$filter", filterQuery);
|
|
564
|
+
if (select?.length) params.set("$select", select.join(","));
|
|
565
|
+
if (expand?.length) params.set("$expand", expand.join(","));
|
|
566
|
+
if (orderBy) params.set("$orderby", orderBy);
|
|
567
|
+
if (top) params.set("$top", String(top));
|
|
568
|
+
if (skip) params.set("$skip", String(skip));
|
|
569
|
+
const queryString = params.toString();
|
|
570
|
+
const url = buildGraphUrl(siteId, "lists", listId, "items") + (queryString ? `?expand=fields&${queryString}` : "?expand=fields");
|
|
571
|
+
const response = await request(url);
|
|
572
|
+
return response.value.map((item) => ({
|
|
573
|
+
id: item.id,
|
|
574
|
+
fields: item.fields,
|
|
575
|
+
contentType: item.fields.ContentType ? { id: "", name: item.fields.ContentType.Name } : void 0,
|
|
576
|
+
createdDateTime: item.createdDateTime,
|
|
577
|
+
lastModifiedDateTime: item.lastModifiedDateTime,
|
|
578
|
+
createdBy: item.createdBy,
|
|
579
|
+
lastModifiedBy: item.lastModifiedBy,
|
|
580
|
+
webUrl: item.webUrl
|
|
581
|
+
}));
|
|
582
|
+
},
|
|
583
|
+
async getItem(options) {
|
|
584
|
+
const { listId, itemId, select, expand } = options;
|
|
585
|
+
const params = new URLSearchParams();
|
|
586
|
+
if (select?.length) params.set("$select", select.join(","));
|
|
587
|
+
if (expand?.length) params.set("$expand", expand.join(","));
|
|
588
|
+
const queryString = params.toString();
|
|
589
|
+
const url = buildGraphUrl(siteId, "lists", listId, "items", itemId) + (queryString ? `?expand=fields&${queryString}` : "?expand=fields");
|
|
590
|
+
const item = await request(url);
|
|
591
|
+
return {
|
|
592
|
+
id: item.id,
|
|
593
|
+
fields: item.fields,
|
|
594
|
+
createdDateTime: item.createdDateTime,
|
|
595
|
+
lastModifiedDateTime: item.lastModifiedDateTime
|
|
596
|
+
};
|
|
597
|
+
},
|
|
598
|
+
async createItem(options) {
|
|
599
|
+
const { listId, fields, contentTypeId } = options;
|
|
600
|
+
const url = buildGraphUrl(siteId, "lists", listId, "items");
|
|
601
|
+
const body = { fields };
|
|
602
|
+
if (contentTypeId) {
|
|
603
|
+
body.fields["ContentType@odata.type"] = contentTypeId;
|
|
604
|
+
}
|
|
605
|
+
const item = await request(url, {
|
|
606
|
+
method: "POST",
|
|
607
|
+
body: JSON.stringify(body)
|
|
608
|
+
});
|
|
609
|
+
return { id: item.id, fields: item.fields };
|
|
610
|
+
},
|
|
611
|
+
async updateItem(options) {
|
|
612
|
+
const { listId, itemId, fields } = options;
|
|
613
|
+
const url = buildGraphUrl(siteId, "lists", listId, "items", itemId, "fields");
|
|
614
|
+
const updatedFields = await request(url, {
|
|
615
|
+
method: "PATCH",
|
|
616
|
+
body: JSON.stringify(fields)
|
|
617
|
+
});
|
|
618
|
+
return { id: itemId, fields: updatedFields };
|
|
619
|
+
},
|
|
620
|
+
async deleteItem(options) {
|
|
621
|
+
const { listId, itemId } = options;
|
|
622
|
+
const url = buildGraphUrl(siteId, "lists", listId, "items", itemId);
|
|
623
|
+
await request(url, { method: "DELETE" });
|
|
624
|
+
},
|
|
625
|
+
async getSites(options) {
|
|
626
|
+
const params = new URLSearchParams();
|
|
627
|
+
if (options?.search) params.set("search", options.search);
|
|
628
|
+
const queryString = params.toString();
|
|
629
|
+
const url = `https://graph.microsoft.com/v1.0/sites${queryString ? `?${queryString}` : ""}`;
|
|
630
|
+
const response = await request(url);
|
|
631
|
+
return response.value;
|
|
632
|
+
},
|
|
633
|
+
async getLists() {
|
|
634
|
+
const url = buildGraphUrl(siteId, "lists");
|
|
635
|
+
const response = await request(url);
|
|
636
|
+
return response.value;
|
|
637
|
+
},
|
|
638
|
+
async getList(options) {
|
|
639
|
+
const url = buildGraphUrl(siteId, "lists", options.listId);
|
|
640
|
+
return request(url);
|
|
641
|
+
},
|
|
642
|
+
async getContentTypes(options) {
|
|
643
|
+
const url = options?.listId ? buildGraphUrl(siteId, "lists", options.listId, "contentTypes") : buildGraphUrl(siteId, "contentTypes");
|
|
644
|
+
const response = await request(url);
|
|
645
|
+
return response.value;
|
|
646
|
+
},
|
|
647
|
+
async getListContentTypes(options) {
|
|
648
|
+
const url = buildGraphUrl(siteId, "lists", options.listId, "contentTypes");
|
|
649
|
+
const response = await request(url);
|
|
650
|
+
return response.value;
|
|
651
|
+
},
|
|
652
|
+
async getColumns(options) {
|
|
653
|
+
const url = buildGraphUrl(siteId, "contentTypes", options.contentTypeId, "columns");
|
|
654
|
+
const response = await request(url);
|
|
655
|
+
return response.value;
|
|
656
|
+
},
|
|
657
|
+
async getListColumns(options) {
|
|
658
|
+
const url = buildGraphUrl(siteId, "lists", options.listId, "columns");
|
|
659
|
+
const response = await request(url);
|
|
660
|
+
return response.value;
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// src/cli/generate-types.ts
|
|
666
|
+
async function generateTypes(options) {
|
|
667
|
+
const { configPath, nonInteractive = false, strategy, clearCache = false } = options;
|
|
668
|
+
console.log("\nSharePoint Kit - Type Generator\n");
|
|
669
|
+
const config = await loadConfig(configPath);
|
|
670
|
+
const outputDir = config.options?.outputDir ?? "./generated";
|
|
671
|
+
const outputFile = config.options?.outputFile ?? "sp-types.ts";
|
|
672
|
+
const cacheManager = new CacheManager(outputDir);
|
|
673
|
+
if (clearCache) {
|
|
674
|
+
cacheManager.clear();
|
|
675
|
+
console.log("Cache cleared.\n");
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
const globalStrategy = strategy ?? config.defaultStrategy ?? "interactive";
|
|
679
|
+
console.log(`Site: ${config.siteId}`);
|
|
680
|
+
console.log(`Strategy: ${globalStrategy}`);
|
|
681
|
+
console.log(`Output: ${outputDir}/${outputFile}
|
|
682
|
+
`);
|
|
683
|
+
const getAccessToken = await createTokenProvider(config);
|
|
684
|
+
const client = createSpClient({
|
|
685
|
+
siteId: config.siteId,
|
|
686
|
+
getAccessToken
|
|
687
|
+
});
|
|
688
|
+
console.log("Scanning SharePoint site...\n");
|
|
689
|
+
const allResolved = [];
|
|
690
|
+
for (const ctConfig of config.contentTypes) {
|
|
691
|
+
console.log(`Processing: "${ctConfig.contentTypeName}" -> ${ctConfig.outputType}`);
|
|
692
|
+
try {
|
|
693
|
+
const resolved = await resolveContentType(
|
|
694
|
+
ctConfig,
|
|
695
|
+
config.siteId,
|
|
696
|
+
client,
|
|
697
|
+
cacheManager,
|
|
698
|
+
globalStrategy,
|
|
699
|
+
nonInteractive
|
|
700
|
+
);
|
|
701
|
+
allResolved.push(...resolved);
|
|
702
|
+
} catch (error) {
|
|
703
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
704
|
+
console.error(` Error: ${message}`);
|
|
705
|
+
if (nonInteractive) {
|
|
706
|
+
throw error;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
if (allResolved.length === 0) {
|
|
711
|
+
console.log("\nNo content types resolved. Nothing to generate.\n");
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
console.log(`
|
|
715
|
+
Generating TypeScript interfaces...
|
|
716
|
+
`);
|
|
717
|
+
const tsContent = await generateTypeScript(
|
|
718
|
+
allResolved,
|
|
719
|
+
client,
|
|
720
|
+
{ fieldNameMapping: config.options?.fieldNameMapping }
|
|
721
|
+
);
|
|
722
|
+
const outputPath = resolve2(process.cwd(), outputDir, outputFile);
|
|
723
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
724
|
+
writeFileSync2(outputPath, tsContent, "utf-8");
|
|
725
|
+
console.log(`
|
|
726
|
+
Done! Generated ${allResolved.length} interface(s).`);
|
|
727
|
+
console.log(`Output: ${outputPath}
|
|
728
|
+
`);
|
|
729
|
+
}
|
|
730
|
+
async function createTokenProvider(config) {
|
|
731
|
+
if (!config.tenantId || !config.clientId) {
|
|
732
|
+
throw new Error(
|
|
733
|
+
"tenantId and clientId are required in config for authentication. You can set them via environment variables: SHAREPOINT_TENANT_ID, SHAREPOINT_CLIENT_ID"
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
const clientSecret = config.clientSecret ?? process.env.SHAREPOINT_CLIENT_SECRET;
|
|
737
|
+
if (!clientSecret) {
|
|
738
|
+
throw new Error(
|
|
739
|
+
"clientSecret is required for authentication. Set it in config or via SHAREPOINT_CLIENT_SECRET environment variable"
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
const msalConfig = {
|
|
743
|
+
auth: {
|
|
744
|
+
clientId: config.clientId,
|
|
745
|
+
authority: `https://login.microsoftonline.com/${config.tenantId}`,
|
|
746
|
+
clientSecret
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
const cca = new ConfidentialClientApplication(msalConfig);
|
|
750
|
+
return async () => {
|
|
751
|
+
const result = await cca.acquireTokenByClientCredential({
|
|
752
|
+
scopes: ["https://graph.microsoft.com/.default"]
|
|
753
|
+
});
|
|
754
|
+
if (!result?.accessToken) {
|
|
755
|
+
throw new Error("Failed to acquire access token");
|
|
756
|
+
}
|
|
757
|
+
return result.accessToken;
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// bin/sp-generate-types.ts
|
|
762
|
+
var program = new Command();
|
|
763
|
+
program.name("sp-generate-types").description("Generate TypeScript interfaces from SharePoint content types").version("0.1.0").requiredOption("-c, --config <path>", "Path to sharepoint.config.ts or .json file").option("--non-interactive", "Run in non-interactive mode (for CI/CD)", false).option(
|
|
764
|
+
"-s, --strategy <strategy>",
|
|
765
|
+
"List selection strategy when multiple lists found (interactive|first|error|all)"
|
|
766
|
+
).option("--clear-cache", "Clear the resolution cache and exit", false).option("--update-cache", "Force update the cache", false).action(async (opts) => {
|
|
767
|
+
try {
|
|
768
|
+
await generateTypes({
|
|
769
|
+
configPath: opts.config,
|
|
770
|
+
nonInteractive: opts.nonInteractive,
|
|
771
|
+
strategy: opts.strategy,
|
|
772
|
+
clearCache: opts.clearCache,
|
|
773
|
+
updateCache: opts.updateCache
|
|
774
|
+
});
|
|
775
|
+
} catch (error) {
|
|
776
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
777
|
+
console.error(`
|
|
778
|
+
Error: ${message}
|
|
779
|
+
`);
|
|
780
|
+
process.exit(1);
|
|
781
|
+
}
|
|
782
|
+
});
|
|
783
|
+
program.parse();
|
|
784
|
+
//# sourceMappingURL=sp-generate-types.js.map
|