Support Power Platform - Apps - Automate - SharePoint - Copilot

[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

  1. Aller sur portal.azure.com
  2. Azure Active Directory › Inscriptions d’applications › AppOffice365
  3. Certificats et secrets › onglet Certificats › Télécharger le certificat
  4. Choisir le fichier scripts/appcert.cer
  5. Noter le thumbprint affiché après l’upload

Étape 7 — Ajouter la permission SharePoint Application

  1. Autorisations API › Ajouter une autorisation
  2. Choisir SharePoint (pas Microsoft Graph)
  3. Cliquer Autorisations d’application (pas Déléguées)
  4. Cocher Sites.FullControl.All
  5. Cliquer Accorder le consentement administrateur pour SARL IN SET

Résultat attendu :

APIAutorisationTypeStatut
SharePointSites.FullControl.AllApplicationAccordé

É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/
│   ├── 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.json

Points 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.All donne un accès total à tous les sites — envisager Sites.Selected en production pour limiter le périmètre