Skip to content

[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
Azure Active DirectoryInscriptions d’applicationsAppOffice365
Certificats et secrets › onglet CertificatsTé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 APIAjouter 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é
There are no rows in this table

É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.com
Ne 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-list
Ré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/
Want to print your doc?
This is not the way.
Try clicking the ··· in the right corner or using a keyboard shortcut (
CtrlP
) instead.