[Dev] Création d’une liste SharePoint depuis Node.js
Stack : SPFx + PnPjs v4 + certificat Azure AD
Phase 1 — Préparation du projet
Étape 1 — Créer le projet SPFx
md fil-rouge-webpart && cd fil-rouge-webpart
yo @microsoft/sharepoint
# Répondre : WebPart › React › TypeScript › nom : VueProdÉtape 2 — Installer les dépendances
npm install @pnp/sp @pnp/nodejs dotenv node-forge
npm install --save-dev ts-node @types/node @types/node-forgeÉtape 3 — Créer tsconfig.scripts.json à la racine
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": false,
"outDir": "./dist-scripts"
},
"include": ["scripts/**/*.ts"]
}Étape 4 — Ajouter les scripts dans package.json
Dans la section "scripts" du fichier package.json existant, ajouter :
"export-key": "ts-node --project tsconfig.scripts.json scripts/exportKey.ts",
"create-list": "ts-node --project tsconfig.scripts.json scripts/createList.ts"Phase 2 — Certificat et Azure AD
Étape 5 — Générer le certificat (PowerShell en administrateur)
$cert = New-SelfSignedCertificate `
-Subject "CN=AppOffice365" `
-CertStoreLocation "Cert:\CurrentUser\My" `
-KeyExportPolicy Exportable `
-KeySpec Signature `
-KeyLength 2048 `
-HashAlgorithm SHA256 `
-NotAfter (Get-Date).AddYears(2)
# Exporter le certificat public (.cer) pour Azure AD
Export-Certificate -Cert $cert `
-FilePath ".\scripts\appcert.cer"
# Exporter la clé privée (.pfx) pour Node.js
$pwd = ConvertTo-SecureString -String "CertPassword123!" -Force -AsPlainText
Export-PfxCertificate -Cert $cert `
-FilePath ".\scripts\appcert.pfx" `
-Password $pwdÉtape 6 — Uploader le certificat dans Azure AD
- Aller sur portal.azure.com
- Azure Active Directory › Inscriptions d’applications ›
AppOffice365 - Certificats et secrets › onglet Certificats › Télécharger le certificat
- Choisir le fichier
scripts/appcert.cer - Noter le thumbprint affiché après l’upload
Étape 7 — Ajouter la permission SharePoint Application
- Autorisations API › Ajouter une autorisation
- Choisir SharePoint (pas Microsoft Graph)
- Cliquer Autorisations d’application (pas Déléguées)
- Cocher
Sites.FullControl.All - Cliquer Accorder le consentement administrateur pour SARL IN SET
Résultat attendu :
| API | Autorisation | Type | Statut |
| SharePoint | Sites.FullControl.All | Application | Accordé |
Étape 8 — Créer scripts/exportKey.ts et convertir la clé
import * as forge from "node-forge";
import * as fs from "fs";
const pfx = forge.pkcs12.pkcs12FromAsn1(
forge.asn1.fromDer(
forge.util.createBuffer(
fs.readFileSync("./scripts/appcert.pfx").toString("binary")
)
),
"CertPassword123!"
);
const bags = pfx.getBags({
bagType: forge.pki.oids.pkcs8ShroudedKeyBag
});
const pem = forge.pki.privateKeyToPem(
bags[forge.pki.oids.pkcs8ShroudedKeyBag]![0].key!
);
fs.writeFileSync("./scripts/private.key", pem, "utf-8");
console.log("Clé PEM exportée avec succès.");Lancer la conversion :
npm run export-keyÉtape 9 — Créer le fichier .env
TENANT_ID=xxx
CLIENT_ID=xxx
THUMBPRINT=xxx
KEY_PATH=./scripts/private.key
SITE_URL=https://<tenant>.sharepoint.com/sites/<site>
SP_ROOT_URL=https://<<tenant>.sharepoint.comNe jamais commiter ce fichier dans Git — l’ajouter dans .gitignore.Phase 3 — Script de création et exécution
Étape 10 — Créer scripts/createList.ts
import "dotenv/config";
import { spfi } from "@pnp/sp";
import { SPDefault } from "@pnp/nodejs";
import * as fs from "fs";
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/fields";
const TENANT_ID = process.env.TENANT_ID!;
const CLIENT_ID = process.env.CLIENT_ID!;
const THUMBPRINT = process.env.THUMBPRINT!;
const KEY_PATH = process.env.KEY_PATH!;
const SITE_URL = process.env.SITE_URL!;
const SP_ROOT_URL = process.env.SP_ROOT_URL!;
async function createSessionsList(): Promise<void> {
const privateKey = fs.readFileSync(KEY_PATH, "utf-8").trim();
const sp = spfi(SITE_URL).using(
SPDefault({
baseUrl: SITE_URL,
msal: {
config: {
auth: {
clientId: CLIENT_ID,
authority: `https://login.microsoftonline.com/${TENANT_ID}`,
clientCertificate: {
thumbprint: THUMBPRINT,
privateKey: privateKey
}
}
},
scopes: [`${SP_ROOT_URL}/.default`]
}
})
);
console.log("Création de la liste Sessions...");
await sp.web.lists.add("Sessions", "Liste des sessions de formation", 100);
const list = sp.web.lists.getByTitle("Sessions");
console.log("Ajout des colonnes...");
// Colonnes texte
for (const col of [
"CodeFormation", "HeureDebut", "NumeroSession",
"TypeFormation", "NomClient", "Site",
"CoordinationNom", "CoordinationEmail",
"Modalite", "LienTeams", "LienSupport",
"LienParticipants", "NomAgence", "EmailAgence",
"LienEmargement", "LienEvaluation", "FichierIcs"
]) {
await list.fields.addText(col);
console.log(` + ${col}`);
}
// Colonnes date
await list.fields.addDateTime("DateSession");
await list.fields.addDateTime("DateFin");
console.log(" + DateSession, DateFin");
// Colonnes nombre
await list.fields.addNumber("Duree");
await list.fields.addNumber("NbConfirmes");
await list.fields.addNumber("NbOption");
await list.fields.addNumber("NbDistanciel");
console.log(" + Duree, NbConfirmes, NbOption, NbDistanciel");
// Colonne texte long
await list.fields.addMultilineText("EnjeuxContexte");
console.log(" + EnjeuxContexte");
// Colonne choix
await list.fields.addChoice("Statut", {
Choices: ["Confirmé", "Option"]
});
console.log(" + Statut");
console.log("Liste Sessions créée avec succès.");
}
createSessionsList()
.then(() => process.exit(0))
.catch((err) => {
console.error("Erreur :", err.message ?? err);
process.exit(1);
});Étape 11 — Lancer la création de la liste
cd C:\Users\gonza\fil-rouge-webpart
npm run create-listRésultat attendu :
Création de la liste Sessions...
Ajout des colonnes...
+ CodeFormation
+ HeureDebut
...
+ Statut
Liste Sessions créée avec succès.Structure finale des fichiers
fil-rouge-webpart/
├── scripts/
│ ├── appcert.cer ← certificat public (uploadé dans Azure AD)
│ ├── appcert.pfx ← certificat + clé privée (ne pas commiter)
│ ├── private.key ← clé PEM exportée (ne pas commiter)
│ ├── exportKey.ts ← script de conversion PFX → PEM
│ └── createList.ts ← script de création de la liste
├── src/
├── .env ← variables sensibles (ne pas commiter)
├── tsconfig.scripts.json ← config TypeScript pour les scripts
└── package.jsonPoints de sécurité
- Ajouter dans
.gitignore:.env,scripts/appcert.pfx,scripts/private.key - Le certificat a une durée de validité de 2 ans — penser à le renouveler avant expiration
- La permission
Sites.FullControl.Alldonne un accès total à tous les sites — envisagerSites.Selecteden production pour limiter le périmètre