├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .npmignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── AUTHORS ├── LICENSE ├── README.md ├── bower.json ├── debug ├── debug.ts └── example.ts ├── gulpfile.js ├── gulptasks ├── @configuration.js ├── build.js ├── clean.js ├── docs.js ├── index.js ├── lint.js ├── package.js ├── publish.js ├── test.js └── watch.js ├── jsdoc.json ├── lib ├── handlers │ ├── clientsidepages.d.ts │ ├── clientsidepages.js │ ├── composedlook.d.ts │ ├── composedlook.js │ ├── contenttypes.d.ts │ ├── contenttypes.js │ ├── customactions.d.ts │ ├── customactions.js │ ├── exports.d.ts │ ├── exports.js │ ├── features.d.ts │ ├── features.js │ ├── files.d.ts │ ├── files.js │ ├── handlerbase.d.ts │ ├── handlerbase.js │ ├── lists.d.ts │ ├── lists.js │ ├── navigation.d.ts │ ├── navigation.js │ ├── propertybagentries.d.ts │ ├── propertybagentries.js │ ├── sitefields.d.ts │ ├── sitefields.js │ ├── websettings.d.ts │ └── websettings.js ├── index.d.ts ├── index.js ├── provisioningconfig.d.ts ├── provisioningconfig.js ├── provisioningcontext.d.ts ├── provisioningcontext.js ├── schema.d.ts ├── schema.js ├── util │ ├── index.d.ts │ ├── index.js │ ├── tokenhelper.d.ts │ └── tokenhelper.js ├── webprovisioner.d.ts └── webprovisioner.js ├── package-lock.json ├── package.json ├── sample-schemas └── all-simple.ts ├── settings.example.js ├── shrinkwrap.yaml ├── src ├── handlers │ ├── clientsidepages.ts │ ├── composedlook.ts │ ├── contenttypes.ts │ ├── customactions.ts │ ├── exports.ts │ ├── features.ts │ ├── files.ts │ ├── handlerbase.ts │ ├── lists.ts │ ├── navigation.ts │ ├── propertybagentries.ts │ ├── sitefields.ts │ └── websettings.ts ├── index.ts ├── provisioningconfig.ts ├── provisioningcontext.ts ├── schema.ts ├── util │ ├── index.ts │ └── tokenhelper.ts └── webprovisioner.ts ├── tests ├── test-config.test.ts └── testutils.ts ├── tsconfig.json ├── tslint.json └── webpack.config.js /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Thank you for reporting an issue, suggesting an enhancement, or asking a question. We appreciate your feedback - to help the team understand your 2 | needs please complete the below template to ensure we have the details to help. Thanks! 3 | 4 | #### Category 5 | [ ] Enhancement 6 | 7 | [ ] Bug 8 | 9 | [ ] Question 10 | 11 | #### Expected / Desired Behavior / Question 12 | _If you are reporting an issue please describe the expected behavior. If you are suggesting an enhancement please 13 | describe thoroughly the enhancement, how it can be achieved, and expected benefit. If you are asking a question, ask away!_ 14 | 15 | #### Observed Behavior 16 | _If you are reporting an issue please describe the behavior you expected to occur when performing the action. If you are making a 17 | suggestion or asking a question delete this section._ 18 | 19 | #### Steps to Reproduce 20 | _If you are reporting an issue please describe the steps to reproduce the bug in sufficient detail to allow testing. If you are making 21 | a suggestion or asking a question delete this section._ 22 | 23 | #### Submission Guidelines 24 | _Delete this section after reading_ 25 | - All suggestions, questions and issues are welcome, please let us know what's on your mind. 26 | - Remember to include sufficient details and context. 27 | - If you have multiple suggestions, questions, or bugs please submit them in seperate issues so we can track resolution. 28 | 29 | Thanks! 30 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | | Q | A 2 | | --------------- | --- 3 | | Bug fix? | [ ] 4 | | New feature? | [ ] 5 | | New sample? | [ ] 6 | | Related issues? | fixes #X, partially #Y, mentioned in #Z 7 | 8 | #### What's in this Pull Request? 9 | 10 | Please describe the changes in this PR. Sample description or details around bugs which are being fixed. 11 | 12 | 13 | #### Guidance 14 | *You can delete this section when you are submitting the pull request.* 15 | * *Please update this PR information accordingly. We'll use this as part of our release notes in monthly communications.* 16 | * *Please target your PR to Dev branch.* 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn.lock 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Coverage directory used by tools like istanbul 13 | coverage 14 | 15 | # node-waf configuration 16 | .lock-wscript 17 | 18 | # Dependency directory 19 | node_modules 20 | 21 | # generated folders 22 | testing 23 | docs 24 | debugging 25 | 26 | # Optional npm cache directory 27 | .npm 28 | 29 | # allow folks to add things to the debug folder, but don't include them in git 30 | debug/* 31 | !debug/debug.ts 32 | !debug/example.ts 33 | 34 | # project settings 35 | settings.js 36 | 37 | bower_components 38 | 39 | # need to include this for bower to work, but leave this ignored as PRs 40 | # with updates to dist will be disallowed 41 | dist/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .npmignore 2 | .github 3 | .bithoundrc 4 | bower.json 5 | gulpfile.js 6 | jsconfig.json 7 | settings.js 8 | settings.example.js 9 | tsconfig.json 10 | tslint.json 11 | typings.json 12 | .vscode/ 13 | coverage/ 14 | server-root/ 15 | src/ 16 | typings/ 17 | tests/ 18 | buildtasks/ 19 | build/ 20 | docs/ 21 | typedoctheme/ -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug", 6 | "type": "node", 7 | "request": "launch", 8 | "program": "${workspaceRoot}/debug/debug.ts", 9 | "stopOnEntry": false, 10 | "args": [], 11 | "cwd": "${workspaceRoot}", 12 | "preLaunchTask": "build:debug", 13 | "runtimeExecutable": null, 14 | "runtimeArgs": [ 15 | "--nolazy" 16 | ], 17 | "env": { 18 | "NODE_ENV": "development" 19 | }, 20 | "console": "internalConsole", 21 | "internalConsoleOptions": "openOnSessionStart", 22 | "sourceMaps": true, 23 | "outFiles": [ 24 | "${workspaceRoot}/debugging/**/*.js" 25 | ] 26 | }, 27 | { 28 | "name": "Gulp Test", 29 | "type": "node", 30 | "request": "launch", 31 | "program": "${workspaceRoot}/node_modules/gulp/bin/gulp.js", 32 | "stopOnEntry": false, 33 | "args": [ 34 | "test" 35 | ], 36 | "cwd": "${workspaceRoot}", 37 | "runtimeExecutable": null, 38 | "runtimeArgs": [ 39 | "--nolazy" 40 | ], 41 | "env": { 42 | "NODE_ENV": "development" 43 | }, 44 | "console": "internalConsole", 45 | "sourceMaps": false 46 | }, 47 | { 48 | "name": "Attach", 49 | "type": "node", 50 | "request": "attach", 51 | "port": 5858 52 | } 53 | ] 54 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "**/.git": true, 5 | "**/.DS_Store": true, 6 | "lib/": true, 7 | "node_modules/": true, 8 | "coverage": true, 9 | "debugging": true 10 | }, 11 | "typescript.tsdk": "node_modules/typescript/lib" 12 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "command": "gulp", 4 | "isShellCommand": true, 5 | "args": [ 6 | "--no-color" 7 | ], 8 | "showOutput": "always", 9 | "tasks": [] 10 | } -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | List of Patterns and Practices Core.JavaScript.Provisioning contributors. Updated before every release. 2 | 3 | Ole Martin Pettersen -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Office 365 Developer Patterns and Practices (PnP) 2 | 3 | The MIT License (MIT) 4 | 5 | Copyright (c) Microsoft Corporation 6 | 7 | All rights reserved. 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Office 365 Developer Patterns and Practices](https://camo.githubusercontent.com/a732087ed949b0f2f84f5f02b8c79f1a9dd96f65/687474703a2f2f692e696d6775722e636f6d2f6c3031686876452e706e67) 2 | 3 | # JavaScript Provisioning Library 4 | 5 | This is the new home of code that started in the [core js library](https://github.com/SharePoint/PnP-JS-Core). It made sense to break it out so 6 | it can grow and develop independently. What is here has been heavily refactored to take advantage of the sp-pnp-js library so that it can work on nodejs. 7 | 8 | Moving forward the [PnP core team](https://dev.office.com/patterns-and-practices) is looking for folks who are interested in developing the provisioning capabilities in nodejs to 9 | work with us to realize that goal. Our recommendation remains using the [CSOM based provisioning engine](https://github.com/SharePoint/PnP-Sites-Core) and calling it from 10 | client script as a microservice. Alternatively you can make use of the [PowerShell wrappers](https://github.com/SharePoint/PnP-powershell) to automate your site provisioning. 11 | 12 | This library should currently be considered a work in progress 13 | 14 | ### Get Started 15 | 16 | **NPM** 17 | 18 | Add the npm packages to your project 19 | 20 | npm install sp-pnp-js sp-pnp-provisioning --save 21 | 22 | ### Authors 23 | This project's contributors include Microsoft and [community contributors](AUTHORS). Work is done as as open source community project. 24 | 25 | ### Code of Conduct 26 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 27 | 28 | ### "Sharing is Caring" 29 | 30 | ### Disclaimer 31 | **THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.** 32 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sp-pnp-js", 3 | "description": "A reusable JavaScript library targeting SharePoint client-side development.", 4 | "main": "dist/pnp.js", 5 | "authors": [ 6 | "Microsoft and other contributors" 7 | ], 8 | "dependencies": { 9 | "es6-promise": "~3.1.2", 10 | "whatwg-fetch": "^0.11.0" 11 | }, 12 | "keywords": [ 13 | "sharepoint", 14 | "office365", 15 | "tools" 16 | ], 17 | "license": "MIT", 18 | "homepage": "https://github.com/OfficeDev/PnP-JS-Core", 19 | "moduleType": [ 20 | "amd", 21 | "globals", 22 | "node" 23 | ], 24 | "ignore": [ 25 | "**/.*", 26 | "gulpfile.js", 27 | "jsconfig.json", 28 | "settings.js", 29 | "settings.example.js", 30 | "package.json", 31 | "tsconfig.json", 32 | "tslint.json", 33 | "server-root/", 34 | "src/", 35 | "tests/", 36 | "typings.json", 37 | "buildtasks/", 38 | "typings.json", 39 | "docs/", 40 | "typedoctheme/" 41 | ], 42 | "repository": { 43 | "type": "git", 44 | "url": "git://github.com/OfficeDev/PnP-JS-Core/" 45 | } 46 | } -------------------------------------------------------------------------------- /debug/debug.ts: -------------------------------------------------------------------------------- 1 | declare var require: any; 2 | import { sp, Web } from "@pnp/sp"; 3 | import { Logger, LogLevel, ConsoleListener } from "@pnp/logging"; 4 | import { SPFetchClient } from "@pnp/nodejs"; 5 | 6 | // setup the connection to SharePoint using the settings file, you can 7 | // override any of the values as you want here, just be sure not to commit 8 | // your account details :) 9 | // if you don't have a settings file defined this will error 10 | // you can comment it out and put the values here directly, or better yet 11 | // create a settings file using settings.example.js as a template 12 | let settings = require("../../settings.js"); 13 | 14 | // configure your node options 15 | sp.setup({ 16 | sp: { 17 | fetchClientFactory: () => { 18 | return new SPFetchClient(settings.testing.siteUrl, settings.testing.clientId, settings.testing.clientSecret); 19 | }, 20 | }, 21 | }); 22 | 23 | // setup console logger 24 | Logger.subscribe(new ConsoleListener()); 25 | Logger.activeLogLevel = LogLevel.Verbose; 26 | 27 | import { Example } from "./example"; 28 | 29 | cleanUpAllSubsites().then(() => { 30 | 31 | // importing the example debug scenario and running it 32 | // adding your debugging to other files and importing them will keep them out of git 33 | // PRs updating the debug.ts or example.ts will not be accepted unless they are fixing bugs 34 | // add your debugging imports here and prior to submitting a PR git checkout debug/debug.ts 35 | // will allow you to keep all your debugging files locally 36 | // comment out the example 37 | Example(); 38 | }); 39 | 40 | 41 | // you can also set break points inside the src folder to examine how things are working 42 | // within the library while debugging! 43 | 44 | // this can be used to clean up lots of test sub webs :) 45 | function cleanUpAllSubsites(): Promise { 46 | 47 | return new Promise((resolve, reject) => { 48 | 49 | sp.site.rootWeb.webs.select("Title").get().then((w) => { 50 | return Promise.all(w.map((element: any) => { 51 | console.log(element["odata.id"]); 52 | let web = new Web(element["odata.id"], ""); 53 | return web.webs.select("Title").get().then((sw: any[]) => { 54 | return Promise.all(sw.map((value) => { 55 | console.log(value["odata.id"]); 56 | let web2 = new Web(value["odata.id"], ""); 57 | return web2.delete().catch(e => { console.error("unable to delete web"); }); 58 | })); 59 | }).then(() => { web.delete(); }).catch(e => { console.error("unable to delete web"); }); 60 | })); 61 | 62 | }).catch(e => { 63 | reject(e); 64 | }).then(() => { 65 | resolve(); 66 | }); 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /debug/example.ts: -------------------------------------------------------------------------------- 1 | // use of relative paths to the modules 2 | import { sp } from "@pnp/sp"; 3 | import { getGUID } from "@pnp/common"; 4 | import { WebProvisioner } from "../src/webprovisioner"; 5 | import { default as template } from "../sample-schemas/all-simple"; 6 | 7 | export function Example() { 8 | 9 | sp.web.webs.add(`Provisioning Debug ${Date.now().toLocaleString()}`, getGUID()).then(war => { 10 | let provisioner = new WebProvisioner(war.web); 11 | 12 | provisioner.applyTemplate(template).then(() => { 13 | 14 | console.log("Template Applied, checking work..."); 15 | 16 | // review the user custom actions 17 | war.web.userCustomActions.get().then(uca => { 18 | console.log(`UCA: ${JSON.stringify(uca)}`); 19 | }); 20 | 21 | // review the features to see if ours was deactivated 22 | war.web.features.get().then(features => { 23 | console.log(`Features: ${JSON.stringify(features)}`); 24 | }); 25 | 26 | // review the quick launch 27 | war.web.navigation.quicklaunch.get().then(ql => { 28 | console.log(`Quick Launch: ${JSON.stringify(ql)}`); 29 | }); 30 | 31 | // review the top navigation 32 | war.web.navigation.topNavigationBar.get().then(tb => { 33 | console.log(`Top Navigation Bar: ${JSON.stringify(tb)}`); 34 | }); 35 | 36 | // review the lists 37 | war.web.lists.get().then(tb => { 38 | console.log(`Lists: ${JSON.stringify(tb)}`); 39 | }); 40 | }); 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | require("./gulptasks"); 2 | -------------------------------------------------------------------------------- /gulptasks/@configuration.js: -------------------------------------------------------------------------------- 1 | // defines the configuration used by the gulp tasks 2 | 3 | function getBanner() { 4 | 5 | let pkg = require("../package.json"); 6 | 7 | return [ 8 | "/**", 9 | ` * ${pkg.name} v${pkg.version} - ${pkg.description}`, 10 | ` * ${pkg.license} (https://github.com/SharePoint/PnP-JS-Provisioning/blob/master/LICENSE)`, 11 | " * Copyright (c) 2016 Microsoft", 12 | " * docs: http://officedev.github.io/PnP-JS-Core", 13 | ` * source: ${pkg.homepage}`, 14 | ` * bugs: ${pkg.bugs.url}`, 15 | ].join("\n"); 16 | } 17 | 18 | function getSettings() { 19 | 20 | try { 21 | return require("../settings.js"); 22 | } catch(e) { 23 | return require("../settings.example.js"); 24 | } 25 | } 26 | 27 | // simplified exports of the config 28 | module.exports = { 29 | paths: { 30 | dist: "./dist", 31 | lib: "./lib", 32 | source: "./src", 33 | sourceGlob: "./src/**/*.ts", 34 | }, 35 | testing: { 36 | testsSource: "./tests", 37 | testsSourceGlob: "./tests/**/*.ts", 38 | testingRoot: "./testing", 39 | testingTestsDest: "./testing/tests", 40 | testingTestsDestGlob: "./testing/tests/**/*.js", 41 | testingSrcDest: "./testing/src", 42 | testingSrcDestGlob: "./testing/src/**/*.js" 43 | }, 44 | debug: { 45 | debugSourceGlob: "./debug/**/*.ts", 46 | outputRoot: "./debugging", 47 | outputSrc: "./debugging/src", 48 | outputDebug: "./debugging/debug", 49 | schemasSourceGlob: "./sample-schemas/**/*.ts", 50 | schemasOutput: "./debugging/sample-schemas" 51 | }, 52 | docs: { 53 | include: "./lib/**/*.js", 54 | output: "./docs" 55 | }, 56 | header: getBanner(), 57 | settings: getSettings() 58 | } 59 | -------------------------------------------------------------------------------- /gulptasks/build.js: -------------------------------------------------------------------------------- 1 | //****************************************************************************** 2 | //* build.js 3 | //* 4 | //* Defines a custom gulp task for compiling TypeScript source code into 5 | //* js files. It outputs the details as to what it generated to the console. 6 | //****************************************************************************** 7 | 8 | var gulp = require("gulp"), 9 | tsc = require("gulp-typescript"), 10 | config = require('./@configuration.js'), 11 | merge = require("merge2"), 12 | sourcemaps = require('gulp-sourcemaps'), 13 | pkg = require("../package.json"); 14 | 15 | gulp.task("build:lib", () => { 16 | 17 | var project = tsc.createProject("tsconfig.json", { declaration: true }); 18 | 19 | var built = gulp.src(config.paths.sourceGlob) 20 | .pipe(project()); 21 | 22 | return merge([ 23 | built.dts.pipe(gulp.dest(config.paths.lib)), 24 | built.js.pipe(gulp.dest(config.paths.lib)) 25 | ]); 26 | }); 27 | 28 | gulp.task("build:testing", ["clean"], () => { 29 | var projectSrc = tsc.createProject("tsconfig.json"); 30 | var projectTests = tsc.createProject("tsconfig.json"); 31 | 32 | return merge([ 33 | gulp.src(config.testing.testsSourceGlob) 34 | .pipe(projectTests({ 35 | compilerOptions: { 36 | types: [ 37 | "chai", 38 | "chai-as-promised", 39 | "node", 40 | "mocha" 41 | ] 42 | } 43 | })) 44 | .pipe(gulp.dest(config.testing.testingTestsDest)), 45 | gulp.src(config.paths.sourceGlob) 46 | .pipe(projectSrc()) 47 | .pipe(gulp.dest(config.testing.testingSrcDest)) 48 | ]); 49 | }); 50 | 51 | gulp.task("build:debug", ["clean"], () => { 52 | 53 | var srcProject = tsc.createProject("tsconfig.json"); 54 | var debugProject = tsc.createProject("tsconfig.json"); 55 | var schemaProject = tsc.createProject("tsconfig.json"); 56 | 57 | let sourceMapSettings = { 58 | includeContent: false, 59 | sourceRoot: (file) => { 60 | return "..\\.." + file.base.replace(file.cwd, ""); 61 | } 62 | }; 63 | 64 | return merge([ 65 | gulp.src(config.paths.sourceGlob) 66 | .pipe(sourcemaps.init()) 67 | .pipe(srcProject()) 68 | .pipe(sourcemaps.write(".", sourceMapSettings)) 69 | .pipe(gulp.dest(config.debug.outputSrc)), 70 | gulp.src(config.debug.debugSourceGlob) 71 | .pipe(sourcemaps.init()) 72 | .pipe(debugProject()) 73 | .pipe(sourcemaps.write(".", sourceMapSettings)) 74 | .pipe(gulp.dest(config.debug.outputDebug)), 75 | gulp.src(config.debug.schemasSourceGlob) 76 | .pipe(sourcemaps.init()) 77 | .pipe(schemaProject()) 78 | .pipe(sourcemaps.write(".", sourceMapSettings)) 79 | .pipe(gulp.dest(config.debug.schemasOutput)) 80 | ]); 81 | }); 82 | 83 | // run the build chain for lib 84 | gulp.task("build", ["clean", "lint", "build:lib"]); 85 | -------------------------------------------------------------------------------- /gulptasks/clean.js: -------------------------------------------------------------------------------- 1 | //****************************************************************************** 2 | //* clean.js 3 | //* 4 | //* Defines a custom gulp task for removing all output files that were 5 | //* autogenerated by other custom gulp tasks 6 | //****************************************************************************** 7 | 8 | var gulp = require("gulp"), 9 | del = require('del'), 10 | config = require('./@configuration.js'); 11 | 12 | gulp.task('clean', (done) => { 13 | 14 | var directories = [ 15 | config.paths.dist, 16 | config.paths.lib, 17 | config.testing.testingRoot, 18 | config.debug.outputRoot, 19 | config.docs.output 20 | ]; 21 | 22 | del(directories).then(() => done()); 23 | }); -------------------------------------------------------------------------------- /gulptasks/docs.js: -------------------------------------------------------------------------------- 1 | var gulp = require("gulp"), 2 | jsdoc = require('gulp-jsdoc3'), 3 | del = require('del'), 4 | jsdocConfig = require('../jsdoc.json'), 5 | config = require('./@configuration.js'); 6 | 7 | gulp.task("docs", ["clean", "build:lib"], (done) => { 8 | 9 | gulp.src(['./README.md', config.docs.include], { read: false }) 10 | .pipe(jsdoc(jsdocConfig, done)); 11 | }); 12 | -------------------------------------------------------------------------------- /gulptasks/index.js: -------------------------------------------------------------------------------- 1 | require("./clean.js"); 2 | require("./lint.js"); 3 | require("./build.js"); 4 | require("./package.js"); 5 | require("./test.js"); 6 | require("./docs.js"); 7 | require("./publish.js"); 8 | require("./watch.js"); 9 | -------------------------------------------------------------------------------- /gulptasks/lint.js: -------------------------------------------------------------------------------- 1 | //****************************************************************************** 2 | //* lint.js 3 | //* 4 | //* Defines a custom gulp task for ensuring that all source code in 5 | //* this repository follows recommended TypeScript practices. 6 | //* 7 | //* Rule violations are output automatically to the console. 8 | //****************************************************************************** 9 | 10 | var gulp = require("gulp"), 11 | tslint = require("gulp-tslint"), 12 | config = require('./@configuration.js'); 13 | 14 | gulp.task("lint", function () { 15 | return gulp.src(config.paths.sourceGlob) 16 | .pipe(tslint({ formatter: "prose" })) 17 | .pipe(tslint.report({ emitError: false })); 18 | }); -------------------------------------------------------------------------------- /gulptasks/package.js: -------------------------------------------------------------------------------- 1 | //****************************************************************************** 2 | //* package.js 3 | //* 4 | //* Defines a custom gulp task for creaing pnp.js, pnp.min.js, 5 | //* and pnp.min.js.map in the dist folder 6 | //****************************************************************************** 7 | var gulp = require("gulp"), 8 | tsc = require("gulp-typescript"), 9 | webpack = require('webpack'), 10 | webpackConfig = require('../webpack.config.js'), 11 | config = require('./@configuration.js'), 12 | gutil = require('gulp-util'); 13 | 14 | // package the definitions 15 | gulp.task("package:defs", () => { 16 | 17 | var typingsProject = tsc.createProject('tsconfig.json', { "declaration": true, "outFile": "pnp.js", "removeComments": false, "module": "system" }); 18 | 19 | gulp.src(config.paths.sourceGlob).pipe(typingsProject()).dts.pipe(gulp.dest(config.paths.dist)); 20 | 21 | }); 22 | 23 | // package the code files using webpack 24 | gulp.task("package:code", ["build:lib"], (done) => { 25 | 26 | webpack(webpackConfig, (err, stats) => { 27 | 28 | if (err) { 29 | throw new gutil.PluginError("package:code", err); 30 | } 31 | 32 | console.log(stats.toString({ 33 | colors: true 34 | })); 35 | 36 | done(); 37 | }); 38 | }); 39 | 40 | // used by the sync task to rebuild code 41 | gulp.task("package:sync", ["package:code"]); 42 | 43 | // run the package chain 44 | gulp.task("package", ["clean", "lint", "package:code", "package:defs"]); 45 | -------------------------------------------------------------------------------- /gulptasks/publish.js: -------------------------------------------------------------------------------- 1 | //****************************************************************************** 2 | //* publish.js 3 | //* 4 | //* Defines a custom gulp task for publishing this repository to npm in 5 | //* both main and beta versions for different branches 6 | //* 7 | //****************************************************************************** 8 | 9 | const 10 | gulp = require("gulp"), 11 | tslint = require("gulp-tslint"), 12 | config = require('./@configuration.js'), 13 | semver = require('semver'), 14 | fs = require('fs'), 15 | package = require("../package.json"), 16 | execSync = require('child_process').execSync, 17 | readline = require('readline'); 18 | 19 | const log = (value) => { console.log(value); return value }; 20 | const exec = (command) => execSync(log(command), { encoding: 'utf8' }); 21 | 22 | function publishSetup() { 23 | 24 | log(`Starting automated npm publish of ${package.name}...`); 25 | } 26 | 27 | function publishBetaSetup() { 28 | 29 | log(`Starting automated npm publish of BETA for ${package.name}...`); 30 | } 31 | 32 | function mergeDevToMaster() { 33 | 34 | log('## Merge dev -> master'); 35 | exec('git checkout dev'); 36 | exec('git pull'); 37 | exec('git checkout master'); 38 | exec('git pull'); 39 | exec('git merge dev'); 40 | log(exec('npm install')); 41 | } 42 | 43 | function incrementPackage() { 44 | 45 | log('## Incrementing the package version'); 46 | const npmVersion = semver.clean(exec(`npm show ${package.name} version`)); 47 | const newVersion = exec('npm version patch'); 48 | 49 | log(`Current version in package.json: ${package.version}`); 50 | log(`Latest version on npm: ${npmVersion}`); 51 | log(`New version after patch: ${newVersion}`); 52 | 53 | if (!semver.gt(newVersion, npmVersion)) { 54 | log('Aborting publish, local version is not new.'); 55 | process.exit(0); 56 | } 57 | log('## Incremented the package version'); 58 | } 59 | 60 | function updateDistFiles() { 61 | 62 | log('## Updating dist files'); 63 | exec('git checkout master'); 64 | log('Updating .gitignore to allow dist/ upload'); 65 | var data = fs.readFileSync('./.gitignore', 'utf-8'); 66 | var newValue = data.replace(/^dist\/$/gim, '#dist/'); 67 | fs.writeFileSync('.gitignore', newValue, 'utf-8'); 68 | log('Updated .gitignore to allow dist/ upload'); 69 | log(exec('git status')); 70 | exec('gulp package'); 71 | log('## Updated dist files'); 72 | } 73 | 74 | function commitDistFiles() { 75 | 76 | log('## Committing Dist Files'); 77 | exec('git add dist/'); 78 | exec('git commit -m "update to dist during master merge"'); 79 | exec('git checkout .gitignore'); 80 | exec('git push'); 81 | log('## Committed Dist Files'); 82 | } 83 | 84 | function publishToNPMGate() { 85 | 86 | log('##'); 87 | log('## -->> The next step will publish the package to NPM!!! <<--'); 88 | log('##'); 89 | } 90 | 91 | function publishToNPM() { 92 | log('## Publishing to NPM'); 93 | log(exec('npm publish')); 94 | log('## Published to NPM'); 95 | } 96 | 97 | function mergeMasterToDev() { 98 | 99 | log('## Merging master -> dev'); 100 | exec('git checkout master'); 101 | exec('git pull'); 102 | exec('git checkout dev'); 103 | exec('git pull'); 104 | exec('git merge master'); 105 | exec('rmdir /S /Q dist'); 106 | exec('git add .'); 107 | exec('git commit -m "clean-up dist for dev branch"'); 108 | exec('git push'); 109 | log('## Merged master -> dev'); 110 | } 111 | 112 | function updateDevForBeta() { 113 | 114 | log('## Updating dev branch for beta release.'); 115 | 116 | exec('git checkout dev'); 117 | exec('git pull'); 118 | exec('npm install'); 119 | 120 | const npmVersion = semver.clean(exec(`npm show ${package.name} version`)); 121 | const newVersion = semver.inc(package.version, 'prerelease', 'beta'); 122 | 123 | log(`Current version in package.json: ${package.version}`); 124 | log(`Latest version on npm: ${npmVersion}`); 125 | log(`New version after patch: ${newVersion}`); 126 | 127 | exec(`npm version ${newVersion}`); 128 | 129 | if (!semver.gt(newVersion, npmVersion)) { 130 | log('Aborting publish, local version is not new.'); 131 | process.exit(0); 132 | } 133 | 134 | log('## Updated dev branch for beta release.'); 135 | } 136 | 137 | function betaPushVersionUpdate() { 138 | 139 | log('## Pushing dev branch for beta release.'); 140 | exec('git push'); 141 | log('## Pushed dev branch for beta release.'); 142 | } 143 | 144 | function betaPackage() { 145 | 146 | log('## Packaging files for BETA release'); 147 | exec('git checkout dev'); 148 | exec('gulp package'); 149 | log('## Packaged files for BETA release'); 150 | } 151 | 152 | function betaPublishToNPM() { 153 | 154 | log('## Publishing to NPM'); 155 | log(exec('npm publish --tag beta')); 156 | log('## Published to NPM'); 157 | } 158 | 159 | function engine(tasks, rl) { 160 | 161 | let task = tasks.shift(); 162 | 163 | task(); 164 | 165 | if (tasks.length > 1) { 166 | 167 | rl.question('Do you want to continue? (/^y(es)?$/i): ', (answer) => { 168 | if (answer.match(/^y(es)?$/i)) { 169 | rl.pause(); 170 | engine(tasks, rl); 171 | } else { 172 | 173 | // run the final cleanup and shutdown task. 174 | tasks.pop()(); 175 | } 176 | }); 177 | } else if (tasks.length === 1) { 178 | 179 | // run the final cleanup and shutdown task. 180 | tasks.pop()(); 181 | } 182 | } 183 | 184 | gulp.task("publish", (done) => { 185 | 186 | const rl = readline.createInterface({ 187 | input: process.stdin, 188 | output: process.stdout 189 | }); 190 | 191 | const publishTasks = [ 192 | publishSetup, 193 | mergeDevToMaster, 194 | incrementPackage, 195 | updateDistFiles, 196 | commitDistFiles, 197 | publishToNPMGate, 198 | publishToNPM, 199 | mergeMasterToDev, 200 | function () { 201 | log('Publishing complete'); 202 | rl.close(); 203 | done(); 204 | }, 205 | ]; 206 | 207 | engine(publishTasks, rl); 208 | }); 209 | 210 | gulp.task("publish-beta", (done) => { 211 | 212 | const rl = readline.createInterface({ 213 | input: process.stdin, 214 | output: process.stdout 215 | }); 216 | 217 | const publishBetaTasks = [ 218 | publishBetaSetup, 219 | updateDevForBeta, 220 | betaPushVersionUpdate, 221 | betaPackage, 222 | publishToNPMGate, 223 | betaPublishToNPM, 224 | function () { 225 | log('BETA Publishing complete'); 226 | rl.close(); 227 | done(); 228 | }, 229 | ]; 230 | 231 | engine(publishBetaTasks, rl); 232 | }); 233 | -------------------------------------------------------------------------------- /gulptasks/test.js: -------------------------------------------------------------------------------- 1 | //****************************************************************************** 2 | //* test.js 3 | //* 4 | //* Defines a custom gulp task for executing the unit tests (with mocha) and 5 | //* also reporting on code coverage (with istanbul). 6 | //****************************************************************************** 7 | 8 | var gulp = require("gulp"), 9 | mocha = require("gulp-mocha"), 10 | istanbul = require("gulp-istanbul"), 11 | tsc = require("gulp-typescript"), 12 | yargs = require('yargs').argv, 13 | config = require('./@configuration.js'), 14 | istanbul = require("gulp-istanbul"); 15 | 16 | 17 | gulp.task("_istanbul:hook", ["build:testing"], () => { 18 | 19 | return gulp.src(config.testing.testingSrcDestGlob) 20 | .pipe(istanbul()) 21 | .pipe(istanbul.hookRequire()); 22 | }); 23 | 24 | gulp.task("test", ["_istanbul:hook"], () => { 25 | 26 | // when using single, grab only that test.js file - otherwise use the entire test.js glob 27 | let path = yargs.single ? './testing/tests/{path}.test.js'.replace('{path}', yargs.single) : config.testing.testingTestsDestGlob; 28 | 29 | // determine if we show the full coverage table 30 | let reports = yargs["coverage-details"] ? ['text', 'text-summary'] : ['text-summary']; 31 | 32 | // easiest way for tests to have settings available 33 | global.settings = config.settings; 34 | 35 | return gulp.src(path) 36 | .pipe(mocha({ ui: 'bdd', reporter: 'dot', timeout: 10000 })) 37 | .pipe(istanbul.writeReports({ 38 | reporters: reports 39 | })); 40 | }); -------------------------------------------------------------------------------- /gulptasks/watch.js: -------------------------------------------------------------------------------- 1 | //****************************************************************************** 2 | //* watch.js 3 | //* 4 | //****************************************************************************** 5 | 6 | var gulp = require("gulp"), 7 | gulpWatch = require("gulp-watch"), 8 | runSequence = require("run-sequence"), 9 | config = require('./@configuration.js'); 10 | 11 | gulp.task("watch", () => { 12 | gulpWatch(config.paths.sourceGlob).on("change", () => { 13 | runSequence("build"); 14 | }); 15 | }); -------------------------------------------------------------------------------- /jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": true, 4 | "dictionaries": [ 5 | "jsdoc", 6 | "closure" 7 | ] 8 | }, 9 | "source": { 10 | "include": [ 11 | "./lib" 12 | ], 13 | "includePattern": "\\.js$" 14 | }, 15 | "plugins": [], 16 | "templates": { 17 | "cleverLinks": false, 18 | "monospaceLinks": false 19 | }, 20 | "opts": { 21 | "template": "templates/default", 22 | "encoding": "utf8", 23 | "destination": "./docs/", 24 | "recurse": true 25 | } 26 | } -------------------------------------------------------------------------------- /lib/handlers/clientsidepages.d.ts: -------------------------------------------------------------------------------- 1 | import { IClientSidePage } from "../schema"; 2 | import { HandlerBase } from "./handlerbase"; 3 | import { Web } from "@pnp/sp"; 4 | import { ProvisioningContext } from "../provisioningcontext"; 5 | import { IProvisioningConfig } from "../provisioningconfig"; 6 | /** 7 | * Describes the Composed Look Object Handler 8 | */ 9 | export declare class ClientSidePages extends HandlerBase { 10 | private tokenHelper; 11 | /** 12 | * Creates a new instance of the ObjectClientSidePages class 13 | */ 14 | constructor(config: IProvisioningConfig); 15 | /** 16 | * Provisioning Client Side Pages 17 | * 18 | * @param {Web} web The web 19 | * @param {IClientSidePage[]} clientSidePages The client side pages to provision 20 | * @param {ProvisioningContext} context Provisioning context 21 | */ 22 | ProvisionObjects(web: Web, clientSidePages: IClientSidePage[], context: ProvisioningContext): Promise; 23 | /** 24 | * Provision a client side page 25 | * 26 | * @param {Web} web The web 27 | * @param {IClientSidePage} clientSidePage Cient side page 28 | */ 29 | private processClientSidePage(web, clientSidePage); 30 | } 31 | -------------------------------------------------------------------------------- /lib/handlers/clientsidepages.js: -------------------------------------------------------------------------------- 1 | var __extends = (this && this.__extends) || (function () { 2 | var extendStatics = Object.setPrototypeOf || 3 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 4 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 5 | return function (d, b) { 6 | extendStatics(d, b); 7 | function __() { this.constructor = d; } 8 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 9 | }; 10 | })(); 11 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 12 | return new (P || (P = Promise))(function (resolve, reject) { 13 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 14 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 15 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 16 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 17 | }); 18 | }; 19 | var __generator = (this && this.__generator) || function (thisArg, body) { 20 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 21 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 22 | function verb(n) { return function (v) { return step([n, v]); }; } 23 | function step(op) { 24 | if (f) throw new TypeError("Generator is already executing."); 25 | while (_) try { 26 | if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; 27 | if (y = 0, t) op = [0, t.value]; 28 | switch (op[0]) { 29 | case 0: case 1: t = op; break; 30 | case 4: _.label++; return { value: op[1], done: false }; 31 | case 5: _.label++; y = op[1]; op = [0]; continue; 32 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 33 | default: 34 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 35 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 36 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 37 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 38 | if (t[2]) _.ops.pop(); 39 | _.trys.pop(); continue; 40 | } 41 | op = body.call(thisArg, _); 42 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 43 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 44 | } 45 | }; 46 | import { HandlerBase } from "./handlerbase"; 47 | import { ClientSideWebpart } from "@pnp/sp"; 48 | import { TokenHelper } from '../util/tokenhelper'; 49 | /** 50 | * Describes the Composed Look Object Handler 51 | */ 52 | var ClientSidePages = (function (_super) { 53 | __extends(ClientSidePages, _super); 54 | /** 55 | * Creates a new instance of the ObjectClientSidePages class 56 | */ 57 | function ClientSidePages(config) { 58 | return _super.call(this, "ClientSidePages", config) || this; 59 | } 60 | /** 61 | * Provisioning Client Side Pages 62 | * 63 | * @param {Web} web The web 64 | * @param {IClientSidePage[]} clientSidePages The client side pages to provision 65 | * @param {ProvisioningContext} context Provisioning context 66 | */ 67 | ClientSidePages.prototype.ProvisionObjects = function (web, clientSidePages, context) { 68 | return __awaiter(this, void 0, void 0, function () { 69 | var _this = this; 70 | var err_1; 71 | return __generator(this, function (_a) { 72 | switch (_a.label) { 73 | case 0: 74 | this.tokenHelper = new TokenHelper(context, this.config); 75 | _super.prototype.scope_started.call(this); 76 | _a.label = 1; 77 | case 1: 78 | _a.trys.push([1, 3, , 4]); 79 | return [4 /*yield*/, clientSidePages.reduce(function (chain, clientSidePage) { return chain.then(function () { return _this.processClientSidePage(web, clientSidePage); }); }, Promise.resolve())]; 80 | case 2: 81 | _a.sent(); 82 | return [3 /*break*/, 4]; 83 | case 3: 84 | err_1 = _a.sent(); 85 | _super.prototype.scope_ended.call(this); 86 | throw err_1; 87 | case 4: return [2 /*return*/]; 88 | } 89 | }); 90 | }); 91 | }; 92 | /** 93 | * Provision a client side page 94 | * 95 | * @param {Web} web The web 96 | * @param {IClientSidePage} clientSidePage Cient side page 97 | */ 98 | ClientSidePages.prototype.processClientSidePage = function (web, clientSidePage) { 99 | return __awaiter(this, void 0, void 0, function () { 100 | var _this = this; 101 | var page, pageItem; 102 | return __generator(this, function (_a) { 103 | switch (_a.label) { 104 | case 0: 105 | _super.prototype.log_info.call(this, "processClientSidePage", "Processing client side page " + clientSidePage.Name); 106 | return [4 /*yield*/, web.addClientSidePage(clientSidePage.Name, clientSidePage.Title, clientSidePage.LibraryTitle)]; 107 | case 1: 108 | page = _a.sent(); 109 | if (!clientSidePage.Sections) return [3 /*break*/, 3]; 110 | clientSidePage.Sections.forEach(function (s) { 111 | var section = page.addSection(); 112 | s.Columns.forEach(function (col) { 113 | var column = section.addColumn(col.Factor); 114 | col.Controls.forEach(function (control) { 115 | var controlJsonString = _this.tokenHelper.replaceTokens(JSON.stringify(control)); 116 | control = JSON.parse(controlJsonString); 117 | _super.prototype.log_info.call(_this, "processClientSidePage", "Adding " + control.Title + " to client side page " + clientSidePage.Name); 118 | column.addControl(new ClientSideWebpart(control.Title, control.Description, control.ClientSideComponentProperties, control.ClientSideComponentId)); 119 | }); 120 | }); 121 | }); 122 | return [4 /*yield*/, page.save()]; 123 | case 2: 124 | _a.sent(); 125 | _a.label = 3; 126 | case 3: return [4 /*yield*/, page.publish()]; 127 | case 4: 128 | _a.sent(); 129 | if (!clientSidePage.CommentsDisabled) return [3 /*break*/, 6]; 130 | _super.prototype.log_info.call(this, "processClientSidePage", "Disabling comments for client side page " + clientSidePage.Name); 131 | return [4 /*yield*/, page.disableComments()]; 132 | case 5: 133 | _a.sent(); 134 | _a.label = 6; 135 | case 6: 136 | if (!clientSidePage.PageLayoutType) return [3 /*break*/, 9]; 137 | _super.prototype.log_info.call(this, "processClientSidePage", "Setting page layout " + clientSidePage.PageLayoutType + " for client side page " + clientSidePage.Name); 138 | return [4 /*yield*/, page.getItem()]; 139 | case 7: 140 | pageItem = _a.sent(); 141 | return [4 /*yield*/, pageItem.update({ PageLayoutType: clientSidePage.PageLayoutType })]; 142 | case 8: 143 | _a.sent(); 144 | _a.label = 9; 145 | case 9: return [2 /*return*/]; 146 | } 147 | }); 148 | }); 149 | }; 150 | return ClientSidePages; 151 | }(HandlerBase)); 152 | export { ClientSidePages }; 153 | -------------------------------------------------------------------------------- /lib/handlers/composedlook.d.ts: -------------------------------------------------------------------------------- 1 | import { IComposedLook } from "../schema"; 2 | import { HandlerBase } from "./handlerbase"; 3 | import { Web } from "@pnp/sp"; 4 | import { IProvisioningConfig } from "../provisioningconfig"; 5 | /** 6 | * Describes the Composed Look Object Handler 7 | */ 8 | export declare class ComposedLook extends HandlerBase { 9 | /** 10 | * Creates a new instance of the ObjectComposedLook class 11 | */ 12 | constructor(config: IProvisioningConfig); 13 | /** 14 | * Provisioning Composed Look 15 | * 16 | * @param {Web} web The web 17 | * @param {IComposedLook} object The Composed Look to provision 18 | */ 19 | ProvisionObjects(web: Web, composedLook: IComposedLook): Promise; 20 | } 21 | -------------------------------------------------------------------------------- /lib/handlers/composedlook.js: -------------------------------------------------------------------------------- 1 | var __extends = (this && this.__extends) || (function () { 2 | var extendStatics = Object.setPrototypeOf || 3 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 4 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 5 | return function (d, b) { 6 | extendStatics(d, b); 7 | function __() { this.constructor = d; } 8 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 9 | }; 10 | })(); 11 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 12 | return new (P || (P = Promise))(function (resolve, reject) { 13 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 14 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 15 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 16 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 17 | }); 18 | }; 19 | var __generator = (this && this.__generator) || function (thisArg, body) { 20 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 21 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 22 | function verb(n) { return function (v) { return step([n, v]); }; } 23 | function step(op) { 24 | if (f) throw new TypeError("Generator is already executing."); 25 | while (_) try { 26 | if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; 27 | if (y = 0, t) op = [0, t.value]; 28 | switch (op[0]) { 29 | case 0: case 1: t = op; break; 30 | case 4: _.label++; return { value: op[1], done: false }; 31 | case 5: _.label++; y = op[1]; op = [0]; continue; 32 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 33 | default: 34 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 35 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 36 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 37 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 38 | if (t[2]) _.ops.pop(); 39 | _.trys.pop(); continue; 40 | } 41 | op = body.call(thisArg, _); 42 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 43 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 44 | } 45 | }; 46 | import { HandlerBase } from "./handlerbase"; 47 | import { replaceUrlTokens, makeUrlRelative } from "../util"; 48 | /** 49 | * Describes the Composed Look Object Handler 50 | */ 51 | var ComposedLook = (function (_super) { 52 | __extends(ComposedLook, _super); 53 | /** 54 | * Creates a new instance of the ObjectComposedLook class 55 | */ 56 | function ComposedLook(config) { 57 | return _super.call(this, "ComposedLook", config) || this; 58 | } 59 | /** 60 | * Provisioning Composed Look 61 | * 62 | * @param {Web} web The web 63 | * @param {IComposedLook} object The Composed Look to provision 64 | */ 65 | ComposedLook.prototype.ProvisionObjects = function (web, composedLook) { 66 | return __awaiter(this, void 0, void 0, function () { 67 | var err_1; 68 | return __generator(this, function (_a) { 69 | switch (_a.label) { 70 | case 0: 71 | _super.prototype.scope_started.call(this); 72 | _a.label = 1; 73 | case 1: 74 | _a.trys.push([1, 3, , 4]); 75 | return [4 /*yield*/, web.applyTheme(makeUrlRelative(replaceUrlTokens(composedLook.ColorPaletteUrl, this.config)), makeUrlRelative(replaceUrlTokens(composedLook.FontSchemeUrl, this.config)), composedLook.BackgroundImageUrl ? makeUrlRelative(replaceUrlTokens(composedLook.BackgroundImageUrl, this.config)) : null, false)]; 76 | case 2: 77 | _a.sent(); 78 | _super.prototype.scope_ended.call(this); 79 | return [3 /*break*/, 4]; 80 | case 3: 81 | err_1 = _a.sent(); 82 | _super.prototype.scope_ended.call(this); 83 | throw err_1; 84 | case 4: return [2 /*return*/]; 85 | } 86 | }); 87 | }); 88 | }; 89 | return ComposedLook; 90 | }(HandlerBase)); 91 | export { ComposedLook }; 92 | -------------------------------------------------------------------------------- /lib/handlers/contenttypes.d.ts: -------------------------------------------------------------------------------- 1 | import { HandlerBase } from "./handlerbase"; 2 | import { Web } from "@pnp/sp"; 3 | import { ProvisioningContext } from "../provisioningcontext"; 4 | import { IProvisioningConfig } from "../provisioningconfig"; 5 | import { IContentType } from '../schema'; 6 | /** 7 | * Describes the Content Types Object Handler 8 | */ 9 | export declare class ContentTypes extends HandlerBase { 10 | private jsomContext; 11 | private context; 12 | /** 13 | * Creates a new instance of the ObjectSiteFields class 14 | */ 15 | constructor(config: IProvisioningConfig); 16 | /** 17 | * Provisioning Content Types 18 | * 19 | * @param {Web} web The web 20 | * @param {IContentType[]} contentTypes The content types 21 | * @param {ProvisioningContext} context Provisioning context 22 | */ 23 | ProvisionObjects(web: Web, contentTypes: IContentType[], context: ProvisioningContext): Promise; 24 | /** 25 | * Provision a content type 26 | * 27 | * @param {Web} web The web 28 | * @param {IContentType} contentType Content type 29 | */ 30 | private processContentType(web, contentType); 31 | /** 32 | * Add a content type 33 | * 34 | * @param {Web} web The web 35 | * @param {IContentType} contentType Content type 36 | */ 37 | private addContentType(web, contentType); 38 | /** 39 | * Adding content type field refs 40 | * 41 | * @param {IContentType} contentType Content type 42 | * @param {SP.ContentType} spContentType Content type 43 | */ 44 | private processContentTypeFieldRefs(contentType, spContentType); 45 | } 46 | -------------------------------------------------------------------------------- /lib/handlers/contenttypes.js: -------------------------------------------------------------------------------- 1 | var __extends = (this && this.__extends) || (function () { 2 | var extendStatics = Object.setPrototypeOf || 3 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 4 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 5 | return function (d, b) { 6 | extendStatics(d, b); 7 | function __() { this.constructor = d; } 8 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 9 | }; 10 | })(); 11 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 12 | return new (P || (P = Promise))(function (resolve, reject) { 13 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 14 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 15 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 16 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 17 | }); 18 | }; 19 | var __generator = (this && this.__generator) || function (thisArg, body) { 20 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 21 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 22 | function verb(n) { return function (v) { return step([n, v]); }; } 23 | function step(op) { 24 | if (f) throw new TypeError("Generator is already executing."); 25 | while (_) try { 26 | if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; 27 | if (y = 0, t) op = [0, t.value]; 28 | switch (op[0]) { 29 | case 0: case 1: t = op; break; 30 | case 4: _.label++; return { value: op[1], done: false }; 31 | case 5: _.label++; y = op[1]; op = [0]; continue; 32 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 33 | default: 34 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 35 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 36 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 37 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 38 | if (t[2]) _.ops.pop(); 39 | _.trys.pop(); continue; 40 | } 41 | op = body.call(thisArg, _); 42 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 43 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 44 | } 45 | }; 46 | import initSpfxJsom, { ExecuteJsomQuery } from "spfx-jsom"; 47 | import { HandlerBase } from "./handlerbase"; 48 | /** 49 | * Describes the Content Types Object Handler 50 | */ 51 | var ContentTypes = (function (_super) { 52 | __extends(ContentTypes, _super); 53 | /** 54 | * Creates a new instance of the ObjectSiteFields class 55 | */ 56 | function ContentTypes(config) { 57 | return _super.call(this, "ContentTypes", config) || this; 58 | } 59 | /** 60 | * Provisioning Content Types 61 | * 62 | * @param {Web} web The web 63 | * @param {IContentType[]} contentTypes The content types 64 | * @param {ProvisioningContext} context Provisioning context 65 | */ 66 | ContentTypes.prototype.ProvisionObjects = function (web, contentTypes, context) { 67 | return __awaiter(this, void 0, void 0, function () { 68 | var _this = this; 69 | var _a, _b, err_1; 70 | return __generator(this, function (_c) { 71 | switch (_c.label) { 72 | case 0: 73 | _a = this; 74 | return [4 /*yield*/, initSpfxJsom(context.web.ServerRelativeUrl)]; 75 | case 1: 76 | _a.jsomContext = _c.sent(); 77 | this.context = context; 78 | _super.prototype.scope_started.call(this); 79 | _c.label = 2; 80 | case 2: 81 | _c.trys.push([2, 5, , 6]); 82 | _b = this.context; 83 | return [4 /*yield*/, web.contentTypes.select('Id', 'Name', 'FieldLinks').expand('FieldLinks').get()]; 84 | case 3: 85 | _b.contentTypes = (_c.sent()).reduce(function (obj, contentType) { 86 | obj[contentType.Name] = { 87 | ID: contentType.Id.StringValue, 88 | Name: contentType.Name, 89 | FieldRefs: contentType.FieldLinks.map(function (fl) { return ({ 90 | ID: fl.Id, 91 | Name: fl.Name, 92 | Required: fl.Required, 93 | Hidden: fl.Hidden, 94 | }); }), 95 | }; 96 | return obj; 97 | }, {}); 98 | return [4 /*yield*/, contentTypes 99 | .sort(function (a, b) { 100 | if (a.ID < b.ID) { 101 | return -1; 102 | } 103 | if (a.ID > b.ID) { 104 | return 1; 105 | } 106 | return 0; 107 | }) 108 | .reduce(function (chain, contentType) { return chain.then(function () { return _this.processContentType(web, contentType); }); }, Promise.resolve())]; 109 | case 4: 110 | _c.sent(); 111 | return [3 /*break*/, 6]; 112 | case 5: 113 | err_1 = _c.sent(); 114 | _super.prototype.scope_ended.call(this); 115 | throw err_1; 116 | case 6: return [2 /*return*/]; 117 | } 118 | }); 119 | }); 120 | }; 121 | /** 122 | * Provision a content type 123 | * 124 | * @param {Web} web The web 125 | * @param {IContentType} contentType Content type 126 | */ 127 | ContentTypes.prototype.processContentType = function (web, contentType) { 128 | return __awaiter(this, void 0, void 0, function () { 129 | var contentTypeId, contentTypeAddResult, spContentType, err_2; 130 | return __generator(this, function (_a) { 131 | switch (_a.label) { 132 | case 0: 133 | _a.trys.push([0, 6, , 7]); 134 | contentTypeId = this.context.contentTypes[contentType.Name].ID; 135 | if (!!contentTypeId) return [3 /*break*/, 2]; 136 | return [4 /*yield*/, this.addContentType(web, contentType)]; 137 | case 1: 138 | contentTypeAddResult = _a.sent(); 139 | contentTypeId = contentTypeAddResult.data.Id; 140 | _a.label = 2; 141 | case 2: 142 | _super.prototype.log_info.call(this, "processContentType", "Processing content type [" + contentType.Name + "] (" + contentTypeId + ")"); 143 | spContentType = this.jsomContext.web.get_contentTypes().getById(contentTypeId); 144 | if (contentType.Description) { 145 | spContentType.set_description(contentType.Description); 146 | } 147 | if (contentType.Group) { 148 | spContentType.set_group(contentType.Group); 149 | } 150 | spContentType.update(true); 151 | return [4 /*yield*/, ExecuteJsomQuery(this.jsomContext)]; 152 | case 3: 153 | _a.sent(); 154 | if (!contentType.FieldRefs) return [3 /*break*/, 5]; 155 | return [4 /*yield*/, this.processContentTypeFieldRefs(contentType, spContentType)]; 156 | case 4: 157 | _a.sent(); 158 | _a.label = 5; 159 | case 5: return [3 /*break*/, 7]; 160 | case 6: 161 | err_2 = _a.sent(); 162 | throw err_2; 163 | case 7: return [2 /*return*/]; 164 | } 165 | }); 166 | }); 167 | }; 168 | /** 169 | * Add a content type 170 | * 171 | * @param {Web} web The web 172 | * @param {IContentType} contentType Content type 173 | */ 174 | ContentTypes.prototype.addContentType = function (web, contentType) { 175 | return __awaiter(this, void 0, void 0, function () { 176 | var err_3; 177 | return __generator(this, function (_a) { 178 | switch (_a.label) { 179 | case 0: 180 | _a.trys.push([0, 2, , 3]); 181 | _super.prototype.log_info.call(this, "addContentType", "Adding content type [" + contentType.Name + "] (" + contentType.ID + ")"); 182 | return [4 /*yield*/, web.contentTypes.add(contentType.ID, contentType.Name, contentType.Description, contentType.Group)]; 183 | case 1: return [2 /*return*/, _a.sent()]; 184 | case 2: 185 | err_3 = _a.sent(); 186 | throw err_3; 187 | case 3: return [2 /*return*/]; 188 | } 189 | }); 190 | }); 191 | }; 192 | /** 193 | * Adding content type field refs 194 | * 195 | * @param {IContentType} contentType Content type 196 | * @param {SP.ContentType} spContentType Content type 197 | */ 198 | ContentTypes.prototype.processContentTypeFieldRefs = function (contentType, spContentType) { 199 | return __awaiter(this, void 0, void 0, function () { 200 | var _loop_1, this_1, i, error_1; 201 | return __generator(this, function (_a) { 202 | switch (_a.label) { 203 | case 0: 204 | _a.trys.push([0, 2, , 3]); 205 | _loop_1 = function (i) { 206 | var fieldRef = contentType.FieldRefs[i]; 207 | var existingFieldLink = this_1.context.contentTypes[contentType.Name].FieldRefs.filter(function (fr) { return fr.Name === fieldRef.Name; })[0]; 208 | var fieldLink = void 0; 209 | if (existingFieldLink) { 210 | fieldLink = spContentType.get_fieldLinks().getById(new SP.Guid(existingFieldLink.ID)); 211 | } 212 | else { 213 | _super.prototype.log_info.call(this_1, "processContentTypeFieldRefs", "Adding field ref " + fieldRef.Name + " to content type [" + contentType.Name + "] (" + contentType.ID + ")"); 214 | var siteField = this_1.jsomContext.web.get_fields().getByInternalNameOrTitle(fieldRef.Name); 215 | var fieldLinkCreationInformation = new SP.FieldLinkCreationInformation(); 216 | fieldLinkCreationInformation.set_field(siteField); 217 | fieldLink = spContentType.get_fieldLinks().add(fieldLinkCreationInformation); 218 | } 219 | if (contentType.FieldRefs[i].hasOwnProperty("Required")) { 220 | fieldLink.set_required(contentType.FieldRefs[i].Required); 221 | } 222 | if (contentType.FieldRefs[i].hasOwnProperty("Hidden")) { 223 | fieldLink.set_hidden(contentType.FieldRefs[i].Hidden); 224 | } 225 | }; 226 | this_1 = this; 227 | for (i = 0; i < contentType.FieldRefs.length; i++) { 228 | _loop_1(i); 229 | } 230 | spContentType.update(true); 231 | return [4 /*yield*/, ExecuteJsomQuery(this.jsomContext)]; 232 | case 1: 233 | _a.sent(); 234 | _super.prototype.log_info.call(this, "processContentTypeFieldRefs", "Successfully processed field refs for content type [" + contentType.Name + "] (" + contentType.ID + ")"); 235 | return [3 /*break*/, 3]; 236 | case 2: 237 | error_1 = _a.sent(); 238 | _super.prototype.log_info.call(this, "processContentTypeFieldRefs", "Failed to process field refs for content type [" + contentType.Name + "] (" + contentType.ID + ")", { error: error_1.args && error_1.args.get_message() }); 239 | return [3 /*break*/, 3]; 240 | case 3: return [2 /*return*/]; 241 | } 242 | }); 243 | }); 244 | }; 245 | return ContentTypes; 246 | }(HandlerBase)); 247 | export { ContentTypes }; 248 | -------------------------------------------------------------------------------- /lib/handlers/customactions.d.ts: -------------------------------------------------------------------------------- 1 | import { HandlerBase } from "./handlerbase"; 2 | import { ICustomAction } from "../schema"; 3 | import { Web } from "@pnp/sp"; 4 | import { IProvisioningConfig } from "../provisioningconfig"; 5 | /** 6 | * Describes the Custom Actions Object Handler 7 | */ 8 | export declare class CustomActions extends HandlerBase { 9 | /** 10 | * Creates a new instance of the ObjectCustomActions class 11 | * 12 | * @param {IProvisioningConfig} config Provisioning config 13 | */ 14 | constructor(config: IProvisioningConfig); 15 | /** 16 | * Provisioning Custom Actions 17 | * 18 | * @param {Web} web The web 19 | * @param {Array} customactions The Custom Actions to provision 20 | */ 21 | ProvisionObjects(web: Web, customActions: ICustomAction[]): Promise; 22 | } 23 | -------------------------------------------------------------------------------- /lib/handlers/customactions.js: -------------------------------------------------------------------------------- 1 | var __extends = (this && this.__extends) || (function () { 2 | var extendStatics = Object.setPrototypeOf || 3 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 4 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 5 | return function (d, b) { 6 | extendStatics(d, b); 7 | function __() { this.constructor = d; } 8 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 9 | }; 10 | })(); 11 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 12 | return new (P || (P = Promise))(function (resolve, reject) { 13 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 14 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 15 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 16 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 17 | }); 18 | }; 19 | var __generator = (this && this.__generator) || function (thisArg, body) { 20 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 21 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 22 | function verb(n) { return function (v) { return step([n, v]); }; } 23 | function step(op) { 24 | if (f) throw new TypeError("Generator is already executing."); 25 | while (_) try { 26 | if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; 27 | if (y = 0, t) op = [0, t.value]; 28 | switch (op[0]) { 29 | case 0: case 1: t = op; break; 30 | case 4: _.label++; return { value: op[1], done: false }; 31 | case 5: _.label++; y = op[1]; op = [0]; continue; 32 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 33 | default: 34 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 35 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 36 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 37 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 38 | if (t[2]) _.ops.pop(); 39 | _.trys.pop(); continue; 40 | } 41 | op = body.call(thisArg, _); 42 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 43 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 44 | } 45 | }; 46 | import { HandlerBase } from "./handlerbase"; 47 | /** 48 | * Describes the Custom Actions Object Handler 49 | */ 50 | var CustomActions = (function (_super) { 51 | __extends(CustomActions, _super); 52 | /** 53 | * Creates a new instance of the ObjectCustomActions class 54 | * 55 | * @param {IProvisioningConfig} config Provisioning config 56 | */ 57 | function CustomActions(config) { 58 | return _super.call(this, "CustomActions", config) || this; 59 | } 60 | /** 61 | * Provisioning Custom Actions 62 | * 63 | * @param {Web} web The web 64 | * @param {Array} customactions The Custom Actions to provision 65 | */ 66 | CustomActions.prototype.ProvisionObjects = function (web, customActions) { 67 | return __awaiter(this, void 0, void 0, function () { 68 | var existingActions_1, batch_1, err_1; 69 | return __generator(this, function (_a) { 70 | switch (_a.label) { 71 | case 0: 72 | _super.prototype.scope_started.call(this); 73 | _a.label = 1; 74 | case 1: 75 | _a.trys.push([1, 4, , 5]); 76 | return [4 /*yield*/, web.userCustomActions.select("Title").get()]; 77 | case 2: 78 | existingActions_1 = _a.sent(); 79 | batch_1 = web.createBatch(); 80 | customActions 81 | .filter(function (action) { 82 | return !existingActions_1.some(function (existingAction) { return existingAction.Title === action.Title; }); 83 | }) 84 | .map(function (action) { 85 | web.userCustomActions.inBatch(batch_1).add(action); 86 | }); 87 | return [4 /*yield*/, batch_1.execute()]; 88 | case 3: 89 | _a.sent(); 90 | _super.prototype.scope_ended.call(this); 91 | return [3 /*break*/, 5]; 92 | case 4: 93 | err_1 = _a.sent(); 94 | _super.prototype.scope_ended.call(this); 95 | throw err_1; 96 | case 5: return [2 /*return*/]; 97 | } 98 | }); 99 | }); 100 | }; 101 | return CustomActions; 102 | }(HandlerBase)); 103 | export { CustomActions }; 104 | -------------------------------------------------------------------------------- /lib/handlers/exports.d.ts: -------------------------------------------------------------------------------- 1 | import { TypedHash } from "@pnp/common"; 2 | import { HandlerBase } from "./handlerbase"; 3 | import { IProvisioningConfig } from "../provisioningconfig"; 4 | export declare const DefaultHandlerMap: (config: IProvisioningConfig) => TypedHash; 5 | export declare const DefaultHandlerSort: TypedHash; 6 | -------------------------------------------------------------------------------- /lib/handlers/exports.js: -------------------------------------------------------------------------------- 1 | import { ComposedLook } from "./composedlook"; 2 | import { CustomActions } from "./customactions"; 3 | import { Features } from "./features"; 4 | import { WebSettings } from "./websettings"; 5 | import { Navigation } from "./navigation"; 6 | import { Lists } from "./lists"; 7 | import { Files } from "./files"; 8 | import { ClientSidePages } from "./clientsidepages"; 9 | import { PropertyBagEntries } from "./propertybagentries"; 10 | import { SiteFields } from "./sitefields"; 11 | import { ContentTypes } from "./contenttypes"; 12 | export var DefaultHandlerMap = function (config) { return ({ 13 | ClientSidePages: new ClientSidePages(config), 14 | ComposedLook: new ComposedLook(config), 15 | ContentTypes: new ContentTypes(config), 16 | CustomActions: new CustomActions(config), 17 | Features: new Features(config), 18 | Files: new Files(config), 19 | Lists: new Lists(config), 20 | Navigation: new Navigation(config), 21 | PropertyBagEntries: new PropertyBagEntries(config), 22 | WebSettings: new WebSettings(config), 23 | SiteFields: new SiteFields(config), 24 | }); }; 25 | export var DefaultHandlerSort = { 26 | ClientSidePages: 7, 27 | ComposedLook: 6, 28 | ContentTypes: 1, 29 | CustomActions: 5, 30 | Features: 2, 31 | Files: 4, 32 | Lists: 3, 33 | Navigation: 9, 34 | PropertyBagEntries: 8, 35 | WebSettings: 10, 36 | SiteFields: 0, 37 | }; 38 | -------------------------------------------------------------------------------- /lib/handlers/features.d.ts: -------------------------------------------------------------------------------- 1 | import { HandlerBase } from "./handlerbase"; 2 | import { IFeature } from "../schema"; 3 | import { Web } from "@pnp/sp"; 4 | import { IProvisioningConfig } from "../provisioningconfig"; 5 | /** 6 | * Describes the Features Object Handler 7 | */ 8 | export declare class Features extends HandlerBase { 9 | /** 10 | * Creates a new instance of the ObjectFeatures class 11 | * 12 | * @param {IProvisioningConfig} config Provisioning config 13 | */ 14 | constructor(config: IProvisioningConfig); 15 | /** 16 | * Provisioning features 17 | * 18 | * @param {Web} web The web 19 | * @param {Array} features The features to provision 20 | */ 21 | ProvisionObjects(web: Web, features: IFeature[]): Promise; 22 | } 23 | -------------------------------------------------------------------------------- /lib/handlers/features.js: -------------------------------------------------------------------------------- 1 | var __extends = (this && this.__extends) || (function () { 2 | var extendStatics = Object.setPrototypeOf || 3 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 4 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 5 | return function (d, b) { 6 | extendStatics(d, b); 7 | function __() { this.constructor = d; } 8 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 9 | }; 10 | })(); 11 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 12 | return new (P || (P = Promise))(function (resolve, reject) { 13 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 14 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 15 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 16 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 17 | }); 18 | }; 19 | var __generator = (this && this.__generator) || function (thisArg, body) { 20 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 21 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 22 | function verb(n) { return function (v) { return step([n, v]); }; } 23 | function step(op) { 24 | if (f) throw new TypeError("Generator is already executing."); 25 | while (_) try { 26 | if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; 27 | if (y = 0, t) op = [0, t.value]; 28 | switch (op[0]) { 29 | case 0: case 1: t = op; break; 30 | case 4: _.label++; return { value: op[1], done: false }; 31 | case 5: _.label++; y = op[1]; op = [0]; continue; 32 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 33 | default: 34 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 35 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 36 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 37 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 38 | if (t[2]) _.ops.pop(); 39 | _.trys.pop(); continue; 40 | } 41 | op = body.call(thisArg, _); 42 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 43 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 44 | } 45 | }; 46 | import { HandlerBase } from "./handlerbase"; 47 | /** 48 | * Describes the Features Object Handler 49 | */ 50 | var Features = (function (_super) { 51 | __extends(Features, _super); 52 | /** 53 | * Creates a new instance of the ObjectFeatures class 54 | * 55 | * @param {IProvisioningConfig} config Provisioning config 56 | */ 57 | function Features(config) { 58 | return _super.call(this, "Features", config) || this; 59 | } 60 | /** 61 | * Provisioning features 62 | * 63 | * @param {Web} web The web 64 | * @param {Array} features The features to provision 65 | */ 66 | Features.prototype.ProvisionObjects = function (web, features) { 67 | return __awaiter(this, void 0, void 0, function () { 68 | var err_1; 69 | return __generator(this, function (_a) { 70 | switch (_a.label) { 71 | case 0: 72 | _super.prototype.scope_started.call(this); 73 | _a.label = 1; 74 | case 1: 75 | _a.trys.push([1, 3, , 4]); 76 | return [4 /*yield*/, features.reduce(function (chain, feature) { 77 | if (feature.deactivate) { 78 | return chain.then(function () { return web.features.remove(feature.id, feature.force); }); 79 | } 80 | else { 81 | return chain.then(function () { return web.features.add(feature.id, feature.force); }); 82 | } 83 | }, Promise.resolve({}))]; 84 | case 2: 85 | _a.sent(); 86 | _super.prototype.scope_ended.call(this); 87 | return [3 /*break*/, 4]; 88 | case 3: 89 | err_1 = _a.sent(); 90 | _super.prototype.scope_ended.call(this); 91 | throw err_1; 92 | case 4: return [2 /*return*/]; 93 | } 94 | }); 95 | }); 96 | }; 97 | return Features; 98 | }(HandlerBase)); 99 | export { Features }; 100 | -------------------------------------------------------------------------------- /lib/handlers/files.d.ts: -------------------------------------------------------------------------------- 1 | import { HandlerBase } from "./handlerbase"; 2 | import { IFile } from "../schema"; 3 | import { Web } from "@pnp/sp"; 4 | import { ProvisioningContext } from "../provisioningcontext"; 5 | import { IProvisioningConfig } from "../provisioningconfig"; 6 | /** 7 | * Describes the Features Object Handler 8 | */ 9 | export declare class Files extends HandlerBase { 10 | private tokenHelper; 11 | /** 12 | * Creates a new instance of the Files class 13 | * 14 | * @param {IProvisioningConfig} config Provisioning config 15 | */ 16 | constructor(config: IProvisioningConfig); 17 | /** 18 | * Provisioning Files 19 | * 20 | * @param {Web} web The web 21 | * @param {IFile[]} files The files to provision 22 | * @param {ProvisioningContext} context Provisioning context 23 | */ 24 | ProvisionObjects(web: Web, files: IFile[], context: ProvisioningContext): Promise; 25 | /** 26 | * Get blob for a file 27 | * 28 | * @param {IFile} file The file 29 | */ 30 | private getFileBlob(file); 31 | /** 32 | * Procceses a file 33 | * 34 | * @param {Web} web The web 35 | * @param {IFile} file The fileAddError 36 | * @param {string} webServerRelativeUrl ServerRelativeUrl for the web 37 | */ 38 | private processFile(web, file, webServerRelativeUrl); 39 | /** 40 | * Remove exisiting webparts if specified 41 | * 42 | * @param {string} webServerRelativeUrl ServerRelativeUrl for the web 43 | * @param {string} fileServerRelativeUrl ServerRelativeUrl for the file 44 | * @param {boolean} shouldRemove Should web parts be removed 45 | */ 46 | private removeExistingWebParts(webServerRelativeUrl, fileServerRelativeUrl, shouldRemove); 47 | /** 48 | * Processes web parts 49 | * 50 | * @param {IFile} file The file 51 | * @param {string} webServerRelativeUrl ServerRelativeUrl for the web 52 | * @param {string} fileServerRelativeUrl ServerRelativeUrl for the file 53 | */ 54 | private processWebParts(file, webServerRelativeUrl, fileServerRelativeUrl); 55 | /** 56 | * Fetches web part contents 57 | * 58 | * @param {Array} webParts Web parts 59 | * @param {Function} callbackFunc Callback function that takes index of the the webpart and the retrieved XML 60 | */ 61 | private fetchWebPartContents; 62 | /** 63 | * Processes page list views 64 | * 65 | * @param {Web} web The web 66 | * @param {Array} webParts Web parts 67 | * @param {string} fileServerRelativeUrl ServerRelativeUrl for the file 68 | */ 69 | private processPageListViews(web, webParts, fileServerRelativeUrl); 70 | /** 71 | * Processes page list view 72 | * 73 | * @param {Web} web The web 74 | * @param {any} listView List view 75 | * @param {string} fileServerRelativeUrl ServerRelativeUrl for the file 76 | */ 77 | private processPageListView(web, listView, fileServerRelativeUrl); 78 | /** 79 | * Process list item properties for the file 80 | * 81 | * @param {Web} web The web 82 | * @param {File} pnpFile The PnP file 83 | * @param {Object} properties The properties to set 84 | */ 85 | private processProperties(web, pnpFile, file); 86 | /** 87 | * Replaces tokens in a string, e.g. {site} 88 | * 89 | * @param {string} str The string 90 | * @param {SP.ClientContext} ctx Client context 91 | */ 92 | private replaceWebPartXmlTokens(str, ctx); 93 | } 94 | -------------------------------------------------------------------------------- /lib/handlers/handlerbase.d.ts: -------------------------------------------------------------------------------- 1 | import { Web } from "@pnp/sp"; 2 | import { ProvisioningContext } from "../provisioningcontext"; 3 | import { IProvisioningConfig } from "../provisioningconfig"; 4 | /** 5 | * Describes the Object Handler Base 6 | */ 7 | export declare class HandlerBase { 8 | config: IProvisioningConfig; 9 | private name; 10 | /** 11 | * Creates a new instance of the ObjectHandlerBase class 12 | * 13 | * @param {string} name Name 14 | * @param {IProvisioningConfig} config Config 15 | */ 16 | constructor(name: string, config?: IProvisioningConfig); 17 | /** 18 | * Provisioning objects 19 | */ 20 | ProvisionObjects(web: Web, templatePart: any, _context?: ProvisioningContext): Promise; 21 | /** 22 | * Writes to Logger when scope has started 23 | */ 24 | scope_started(): void; 25 | /** 26 | * Writes to Logger when scope has stopped 27 | */ 28 | scope_ended(): void; 29 | /** 30 | * Writes to Logger 31 | * 32 | * @param {string} scope Scope 33 | * @param {string} message Message 34 | * @param {any} data Data 35 | */ 36 | log_info(scope: string, message: string, data?: any): void; 37 | } 38 | -------------------------------------------------------------------------------- /lib/handlers/handlerbase.js: -------------------------------------------------------------------------------- 1 | import { Logger } from "@pnp/logging"; 2 | /** 3 | * Describes the Object Handler Base 4 | */ 5 | var HandlerBase = (function () { 6 | /** 7 | * Creates a new instance of the ObjectHandlerBase class 8 | * 9 | * @param {string} name Name 10 | * @param {IProvisioningConfig} config Config 11 | */ 12 | function HandlerBase(name, config) { 13 | if (config === void 0) { config = {}; } 14 | this.config = {}; 15 | this.name = name; 16 | this.config = config; 17 | } 18 | /** 19 | * Provisioning objects 20 | */ 21 | HandlerBase.prototype.ProvisionObjects = function (web, templatePart, _context) { 22 | Logger.log({ data: templatePart, level: 2 /* Warning */, message: "Handler " + this.name + " for web [" + web.toUrl() + "] does not override ProvisionObjects." }); 23 | return Promise.resolve(); 24 | }; 25 | /** 26 | * Writes to Logger when scope has started 27 | */ 28 | HandlerBase.prototype.scope_started = function () { 29 | this.log_info("ProvisionObjects", "Code execution scope started"); 30 | }; 31 | /** 32 | * Writes to Logger when scope has stopped 33 | */ 34 | HandlerBase.prototype.scope_ended = function () { 35 | this.log_info("ProvisionObjects", "Code execution scope ended"); 36 | }; 37 | /** 38 | * Writes to Logger 39 | * 40 | * @param {string} scope Scope 41 | * @param {string} message Message 42 | * @param {any} data Data 43 | */ 44 | HandlerBase.prototype.log_info = function (scope, message, data) { 45 | var prefix = (this.config.logging && this.config.logging.prefix) ? this.config.logging.prefix + " " : ""; 46 | Logger.log({ message: prefix + "(" + this.name + "): " + scope + ": " + message, data: data, level: 1 /* Info */ }); 47 | }; 48 | return HandlerBase; 49 | }()); 50 | export { HandlerBase }; 51 | -------------------------------------------------------------------------------- /lib/handlers/lists.d.ts: -------------------------------------------------------------------------------- 1 | import { HandlerBase } from './handlerbase'; 2 | import { IList } from '../schema'; 3 | import { Web } from '@pnp/sp'; 4 | import { ProvisioningContext } from '../provisioningcontext'; 5 | import { IProvisioningConfig } from '../provisioningconfig'; 6 | /** 7 | * Describes the Lists Object Handler 8 | */ 9 | export declare class Lists extends HandlerBase { 10 | private tokenHelper; 11 | private context; 12 | /** 13 | * Creates a new instance of the Lists class 14 | * 15 | * @param {IProvisioningConfig} config Provisioning config 16 | */ 17 | constructor(config: IProvisioningConfig); 18 | /** 19 | * Provisioning lists 20 | * 21 | * @param {Web} web The web 22 | * @param {Array} lists The lists to provision 23 | */ 24 | ProvisionObjects(web: Web, lists: IList[], context: ProvisioningContext): Promise; 25 | /** 26 | * Processes a list 27 | * 28 | * @param {Web} web The web 29 | * @param {IList} lc The list 30 | */ 31 | private processList(web, lc); 32 | /** 33 | * Processes content type bindings for a list 34 | * 35 | * @param {IList} lc The list configuration 36 | * @param {List} list The pnp list 37 | * @param {Array} contentTypeBindings Content type bindings 38 | * @param {boolean} removeExisting Remove existing content type bindings 39 | */ 40 | private processContentTypeBindings(lc, list, contentTypeBindings, removeExisting); 41 | /** 42 | * Processes a content type binding for a list 43 | * 44 | * @param {IList} lc The list configuration 45 | * @param {List} list The pnp list 46 | * @param {string} contentTypeID The Content Type ID 47 | */ 48 | private processContentTypeBinding(lc, list, contentTypeID); 49 | /** 50 | * Processes fields for a list 51 | * 52 | * @param {Web} web The web 53 | * @param {IList} list The pnp list 54 | */ 55 | private processListFields(web, list); 56 | /** 57 | * Processes a field for a lit 58 | * 59 | * @param {Web} web The web 60 | * @param {IList} lc The list configuration 61 | * @param {string} fieldXml Field xml 62 | */ 63 | private processField(web, lc, fieldXml); 64 | /** 65 | * Processes field refs for a list 66 | * 67 | * @param {Web} web The web 68 | * @param {IList} list The pnp list 69 | */ 70 | private processListFieldRefs(web, list); 71 | /** 72 | * 73 | * Processes a field ref for a list 74 | * 75 | * @param {Web} web The web 76 | * @param {IList} lc The list configuration 77 | * @param {IListInstanceFieldRef} fieldRef The list field ref 78 | */ 79 | private processFieldRef(web, lc, fieldRef); 80 | /** 81 | * Processes views for a list 82 | * 83 | * @param web The web 84 | * @param lc The list configuration 85 | */ 86 | private processListViews(web, lc); 87 | /** 88 | * Processes a view for a list 89 | * 90 | * @param {Web} web The web 91 | * @param {IList} lc The list configuration 92 | * @param {IListView} lvc The view configuration 93 | */ 94 | private processView(web, lc, lvc); 95 | /** 96 | * Processes view fields for a view 97 | * 98 | * @param {any} view The pnp view 99 | * @param {IListView} lvc The view configuration 100 | */ 101 | private processViewFields(view, lvc); 102 | } 103 | -------------------------------------------------------------------------------- /lib/handlers/navigation.d.ts: -------------------------------------------------------------------------------- 1 | import { HandlerBase } from "./handlerbase"; 2 | import { INavigation } from "../schema"; 3 | import { Web } from "@pnp/sp"; 4 | import { IProvisioningConfig } from "../provisioningconfig"; 5 | /** 6 | * Describes the Navigation Object Handler 7 | */ 8 | export declare class Navigation extends HandlerBase { 9 | /** 10 | * Creates a new instance of the Navigation class 11 | * 12 | * @param {IProvisioningConfig} config Provisioning config 13 | */ 14 | constructor(config: IProvisioningConfig); 15 | /** 16 | * Provisioning navigation 17 | * 18 | * @param {Navigation} navigation The navigation to provision 19 | */ 20 | ProvisionObjects(web: Web, navigation: INavigation): Promise; 21 | private processNavTree(target, nodes); 22 | private processNode(target, node, existingNodes); 23 | private deleteExistingNodes(target); 24 | private deleteNode(target, id); 25 | } 26 | -------------------------------------------------------------------------------- /lib/handlers/navigation.js: -------------------------------------------------------------------------------- 1 | var __extends = (this && this.__extends) || (function () { 2 | var extendStatics = Object.setPrototypeOf || 3 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 4 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 5 | return function (d, b) { 6 | extendStatics(d, b); 7 | function __() { this.constructor = d; } 8 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 9 | }; 10 | })(); 11 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 12 | return new (P || (P = Promise))(function (resolve, reject) { 13 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 14 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 15 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 16 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 17 | }); 18 | }; 19 | var __generator = (this && this.__generator) || function (thisArg, body) { 20 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 21 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 22 | function verb(n) { return function (v) { return step([n, v]); }; } 23 | function step(op) { 24 | if (f) throw new TypeError("Generator is already executing."); 25 | while (_) try { 26 | if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; 27 | if (y = 0, t) op = [0, t.value]; 28 | switch (op[0]) { 29 | case 0: case 1: t = op; break; 30 | case 4: _.label++; return { value: op[1], done: false }; 31 | case 5: _.label++; y = op[1]; op = [0]; continue; 32 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 33 | default: 34 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 35 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 36 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 37 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 38 | if (t[2]) _.ops.pop(); 39 | _.trys.pop(); continue; 40 | } 41 | op = body.call(thisArg, _); 42 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 43 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 44 | } 45 | }; 46 | import { HandlerBase } from "./handlerbase"; 47 | import { isArray } from "@pnp/common"; 48 | import { replaceUrlTokens } from "../util"; 49 | /** 50 | * Describes the Navigation Object Handler 51 | */ 52 | var Navigation = (function (_super) { 53 | __extends(Navigation, _super); 54 | /** 55 | * Creates a new instance of the Navigation class 56 | * 57 | * @param {IProvisioningConfig} config Provisioning config 58 | */ 59 | function Navigation(config) { 60 | return _super.call(this, "Navigation", config) || this; 61 | } 62 | /** 63 | * Provisioning navigation 64 | * 65 | * @param {Navigation} navigation The navigation to provision 66 | */ 67 | Navigation.prototype.ProvisionObjects = function (web, navigation) { 68 | return __awaiter(this, void 0, void 0, function () { 69 | var promises, err_1; 70 | return __generator(this, function (_a) { 71 | switch (_a.label) { 72 | case 0: 73 | _super.prototype.scope_started.call(this); 74 | promises = []; 75 | if (isArray(navigation.QuickLaunch)) { 76 | promises.push(this.processNavTree(web.navigation.quicklaunch, navigation.QuickLaunch)); 77 | } 78 | if (isArray(navigation.TopNavigationBar)) { 79 | promises.push(this.processNavTree(web.navigation.topNavigationBar, navigation.TopNavigationBar)); 80 | } 81 | _a.label = 1; 82 | case 1: 83 | _a.trys.push([1, 3, , 4]); 84 | return [4 /*yield*/, Promise.all(promises)]; 85 | case 2: 86 | _a.sent(); 87 | _super.prototype.scope_ended.call(this); 88 | return [3 /*break*/, 4]; 89 | case 3: 90 | err_1 = _a.sent(); 91 | _super.prototype.scope_ended.call(this); 92 | throw err_1; 93 | case 4: return [2 /*return*/]; 94 | } 95 | }); 96 | }); 97 | }; 98 | Navigation.prototype.processNavTree = function (target, nodes) { 99 | return __awaiter(this, void 0, void 0, function () { 100 | var _this = this; 101 | var existingNodes_1, err_2; 102 | return __generator(this, function (_a) { 103 | switch (_a.label) { 104 | case 0: 105 | _a.trys.push([0, 4, , 5]); 106 | return [4 /*yield*/, target.get()]; 107 | case 1: 108 | existingNodes_1 = _a.sent(); 109 | return [4 /*yield*/, this.deleteExistingNodes(target)]; 110 | case 2: 111 | _a.sent(); 112 | return [4 /*yield*/, nodes.reduce(function (chain, node) { return chain.then(function () { return _this.processNode(target, node, existingNodes_1); }); }, Promise.resolve())]; 113 | case 3: 114 | _a.sent(); 115 | return [3 /*break*/, 5]; 116 | case 4: 117 | err_2 = _a.sent(); 118 | throw err_2; 119 | case 5: return [2 /*return*/]; 120 | } 121 | }); 122 | }); 123 | }; 124 | Navigation.prototype.processNode = function (target, node, existingNodes) { 125 | return __awaiter(this, void 0, void 0, function () { 126 | var existingNode, result, err_3; 127 | return __generator(this, function (_a) { 128 | switch (_a.label) { 129 | case 0: 130 | existingNode = existingNodes.filter(function (n) { return n.Title === node.Title; }); 131 | if (existingNode.length === 1 && node.IgnoreExisting !== true) { 132 | node.Url = existingNode[0].Url; 133 | } 134 | _a.label = 1; 135 | case 1: 136 | _a.trys.push([1, 5, , 6]); 137 | return [4 /*yield*/, target.add(node.Title, replaceUrlTokens(node.Url, this.config))]; 138 | case 2: 139 | result = _a.sent(); 140 | if (!isArray(node.Children)) return [3 /*break*/, 4]; 141 | return [4 /*yield*/, this.processNavTree(result.node.children, node.Children)]; 142 | case 3: 143 | _a.sent(); 144 | _a.label = 4; 145 | case 4: return [3 /*break*/, 6]; 146 | case 5: 147 | err_3 = _a.sent(); 148 | throw err_3; 149 | case 6: return [2 /*return*/]; 150 | } 151 | }); 152 | }); 153 | }; 154 | Navigation.prototype.deleteExistingNodes = function (target) { 155 | return __awaiter(this, void 0, void 0, function () { 156 | var _this = this; 157 | var existingNodes, err_4; 158 | return __generator(this, function (_a) { 159 | switch (_a.label) { 160 | case 0: 161 | _a.trys.push([0, 3, , 4]); 162 | return [4 /*yield*/, target.get()]; 163 | case 1: 164 | existingNodes = _a.sent(); 165 | return [4 /*yield*/, existingNodes.reduce(function (chain, n) { return chain.then(function () { return _this.deleteNode(target, n.Id); }); }, Promise.resolve())]; 166 | case 2: 167 | _a.sent(); 168 | return [3 /*break*/, 4]; 169 | case 3: 170 | err_4 = _a.sent(); 171 | throw err_4; 172 | case 4: return [2 /*return*/]; 173 | } 174 | }); 175 | }); 176 | }; 177 | Navigation.prototype.deleteNode = function (target, id) { 178 | return __awaiter(this, void 0, void 0, function () { 179 | var err_5; 180 | return __generator(this, function (_a) { 181 | switch (_a.label) { 182 | case 0: 183 | _a.trys.push([0, 2, , 3]); 184 | return [4 /*yield*/, target.getById(id).delete()]; 185 | case 1: 186 | _a.sent(); 187 | return [3 /*break*/, 3]; 188 | case 2: 189 | err_5 = _a.sent(); 190 | throw err_5; 191 | case 3: return [2 /*return*/]; 192 | } 193 | }); 194 | }); 195 | }; 196 | return Navigation; 197 | }(HandlerBase)); 198 | export { Navigation }; 199 | -------------------------------------------------------------------------------- /lib/handlers/propertybagentries.d.ts: -------------------------------------------------------------------------------- 1 | import { HandlerBase } from "./handlerbase"; 2 | import { IPropertyBagEntry } from "../schema"; 3 | import { Web } from "@pnp/sp"; 4 | import { IProvisioningConfig } from "../provisioningconfig"; 5 | /** 6 | * Describes the PropertyBagEntries Object Handler 7 | */ 8 | export declare class PropertyBagEntries extends HandlerBase { 9 | /** 10 | * Creates a new instance of the PropertyBagEntries class 11 | * 12 | * @param {IProvisioningConfig} config Provisioning config 13 | */ 14 | constructor(config: IProvisioningConfig); 15 | /** 16 | * Provisioning property bag entries 17 | * 18 | * @param {Web} web The web 19 | * @param {Array} entries The property bag entries to provision 20 | */ 21 | ProvisionObjects(web: Web, entries: IPropertyBagEntry[]): Promise; 22 | } 23 | -------------------------------------------------------------------------------- /lib/handlers/propertybagentries.js: -------------------------------------------------------------------------------- 1 | var __extends = (this && this.__extends) || (function () { 2 | var extendStatics = Object.setPrototypeOf || 3 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 4 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 5 | return function (d, b) { 6 | extendStatics(d, b); 7 | function __() { this.constructor = d; } 8 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 9 | }; 10 | })(); 11 | import { HandlerBase } from "./handlerbase"; 12 | import * as Util from "../util"; 13 | import { Logger } from "@pnp/logging"; 14 | /** 15 | * Describes the PropertyBagEntries Object Handler 16 | */ 17 | var PropertyBagEntries = (function (_super) { 18 | __extends(PropertyBagEntries, _super); 19 | /** 20 | * Creates a new instance of the PropertyBagEntries class 21 | * 22 | * @param {IProvisioningConfig} config Provisioning config 23 | */ 24 | function PropertyBagEntries(config) { 25 | return _super.call(this, "PropertyBagEntries", config) || this; 26 | } 27 | /** 28 | * Provisioning property bag entries 29 | * 30 | * @param {Web} web The web 31 | * @param {Array} entries The property bag entries to provision 32 | */ 33 | PropertyBagEntries.prototype.ProvisionObjects = function (web, entries) { 34 | var _this = this; 35 | _super.prototype.scope_started.call(this); 36 | return new Promise(function (resolve, reject) { 37 | if (Util.isNode()) { 38 | Logger.write("PropertyBagEntries Handler not supported in Node.", 3 /* Error */); 39 | reject(); 40 | } 41 | else if (_this.config.spfxContext) { 42 | Logger.write("PropertyBagEntries Handler not supported in SPFx.", 3 /* Error */); 43 | reject(); 44 | } 45 | else { 46 | web.get().then(function (_a) { 47 | var ServerRelativeUrl = _a.ServerRelativeUrl; 48 | var ctx = new SP.ClientContext(ServerRelativeUrl), spWeb = ctx.get_web(), propBag = spWeb.get_allProperties(), idxProps = []; 49 | entries.filter(function (entry) { return entry.Overwrite; }).forEach(function (entry) { 50 | propBag.set_item(entry.Key, entry.Value); 51 | if (entry.Indexed) { 52 | idxProps.push(Util.base64EncodeString(entry.Key)); 53 | } 54 | }); 55 | spWeb.update(); 56 | ctx.load(propBag); 57 | ctx.executeQueryAsync(function () { 58 | if (idxProps.length > 0) { 59 | propBag.set_item("vti_indexedpropertykeys", idxProps.join("|")); 60 | spWeb.update(); 61 | ctx.executeQueryAsync(function () { 62 | _super.prototype.scope_ended.call(_this); 63 | resolve(); 64 | }, function () { 65 | _super.prototype.scope_ended.call(_this); 66 | reject(); 67 | }); 68 | } 69 | else { 70 | _super.prototype.scope_ended.call(_this); 71 | resolve(); 72 | } 73 | }, function () { 74 | _super.prototype.scope_ended.call(_this); 75 | reject(); 76 | }); 77 | }); 78 | } 79 | }); 80 | }; 81 | return PropertyBagEntries; 82 | }(HandlerBase)); 83 | export { PropertyBagEntries }; 84 | -------------------------------------------------------------------------------- /lib/handlers/sitefields.d.ts: -------------------------------------------------------------------------------- 1 | import { HandlerBase } from "./handlerbase"; 2 | import { Web } from "@pnp/sp"; 3 | import { ProvisioningContext } from "../provisioningcontext"; 4 | import { IProvisioningConfig } from "../provisioningconfig"; 5 | /** 6 | * Describes the Site Fields Object Handler 7 | */ 8 | export declare class SiteFields extends HandlerBase { 9 | private context; 10 | private tokenHelper; 11 | /** 12 | * Creates a new instance of the ObjectSiteFields class 13 | */ 14 | constructor(config: IProvisioningConfig); 15 | /** 16 | * Provisioning Client Side Pages 17 | * 18 | * @param {Web} web The web 19 | * @param {string[]} siteFields The site fields 20 | * @param {ProvisioningContext} context Provisioning context 21 | */ 22 | ProvisionObjects(web: Web, siteFields: string[], context: ProvisioningContext): Promise; 23 | /** 24 | * Provision a site field 25 | * 26 | * @param {Web} web The web 27 | * @param {IClientSidePage} clientSidePage Cient side page 28 | */ 29 | private processSiteField(web, schemaXml); 30 | } 31 | -------------------------------------------------------------------------------- /lib/handlers/sitefields.js: -------------------------------------------------------------------------------- 1 | var __extends = (this && this.__extends) || (function () { 2 | var extendStatics = Object.setPrototypeOf || 3 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 4 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 5 | return function (d, b) { 6 | extendStatics(d, b); 7 | function __() { this.constructor = d; } 8 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 9 | }; 10 | })(); 11 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 12 | return new (P || (P = Promise))(function (resolve, reject) { 13 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 14 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 15 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 16 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 17 | }); 18 | }; 19 | var __generator = (this && this.__generator) || function (thisArg, body) { 20 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 21 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 22 | function verb(n) { return function (v) { return step([n, v]); }; } 23 | function step(op) { 24 | if (f) throw new TypeError("Generator is already executing."); 25 | while (_) try { 26 | if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; 27 | if (y = 0, t) op = [0, t.value]; 28 | switch (op[0]) { 29 | case 0: case 1: t = op; break; 30 | case 4: _.label++; return { value: op[1], done: false }; 31 | case 5: _.label++; y = op[1]; op = [0]; continue; 32 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 33 | default: 34 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 35 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 36 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 37 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 38 | if (t[2]) _.ops.pop(); 39 | _.trys.pop(); continue; 40 | } 41 | op = body.call(thisArg, _); 42 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 43 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 44 | } 45 | }; 46 | import * as xmljs from 'xml-js'; 47 | import { HandlerBase } from "./handlerbase"; 48 | import { TokenHelper } from '../util/tokenhelper'; 49 | /** 50 | * Describes the Site Fields Object Handler 51 | */ 52 | var SiteFields = (function (_super) { 53 | __extends(SiteFields, _super); 54 | /** 55 | * Creates a new instance of the ObjectSiteFields class 56 | */ 57 | function SiteFields(config) { 58 | return _super.call(this, "SiteFields", config) || this; 59 | } 60 | /** 61 | * Provisioning Client Side Pages 62 | * 63 | * @param {Web} web The web 64 | * @param {string[]} siteFields The site fields 65 | * @param {ProvisioningContext} context Provisioning context 66 | */ 67 | SiteFields.prototype.ProvisionObjects = function (web, siteFields, context) { 68 | return __awaiter(this, void 0, void 0, function () { 69 | var _this = this; 70 | var _a, err_1; 71 | return __generator(this, function (_b) { 72 | switch (_b.label) { 73 | case 0: 74 | this.context = context; 75 | this.tokenHelper = new TokenHelper(this.context, this.config); 76 | _super.prototype.scope_started.call(this); 77 | _b.label = 1; 78 | case 1: 79 | _b.trys.push([1, 4, , 5]); 80 | _a = this.context; 81 | return [4 /*yield*/, web.fields.select('Id', 'InternalName').get()]; 82 | case 2: 83 | _a.siteFields = (_b.sent()).reduce(function (obj, l) { 84 | obj[l.InternalName] = l.Id; 85 | return obj; 86 | }, {}); 87 | return [4 /*yield*/, siteFields.reduce(function (chain, schemaXml) { return chain.then(function () { return _this.processSiteField(web, schemaXml); }); }, Promise.resolve())]; 88 | case 3: 89 | _b.sent(); 90 | return [3 /*break*/, 5]; 91 | case 4: 92 | err_1 = _b.sent(); 93 | _super.prototype.scope_ended.call(this); 94 | throw err_1; 95 | case 5: return [2 /*return*/]; 96 | } 97 | }); 98 | }); 99 | }; 100 | /** 101 | * Provision a site field 102 | * 103 | * @param {Web} web The web 104 | * @param {IClientSidePage} clientSidePage Cient side page 105 | */ 106 | SiteFields.prototype.processSiteField = function (web, schemaXml) { 107 | return __awaiter(this, void 0, void 0, function () { 108 | var schemaXmlJson, _a, DisplayName, Name, err_2; 109 | return __generator(this, function (_b) { 110 | switch (_b.label) { 111 | case 0: 112 | _b.trys.push([0, 5, , 6]); 113 | schemaXml = this.tokenHelper.replaceTokens(schemaXml); 114 | schemaXmlJson = JSON.parse(xmljs.xml2json(schemaXml)); 115 | _a = schemaXmlJson.elements[0].attributes, DisplayName = _a.DisplayName, Name = _a.Name; 116 | if (!this.context.siteFields[Name]) return [3 /*break*/, 2]; 117 | _super.prototype.log_info.call(this, "processSiteField", "Updating site field " + DisplayName); 118 | return [4 /*yield*/, web.fields.getByInternalNameOrTitle(Name).update({ SchemaXml: schemaXml })]; 119 | case 1: return [2 /*return*/, _b.sent()]; 120 | case 2: 121 | _super.prototype.log_info.call(this, "processSiteField", "Adding site field " + DisplayName); 122 | return [4 /*yield*/, web.fields.createFieldAsXml(schemaXml)]; 123 | case 3: return [2 /*return*/, _b.sent()]; 124 | case 4: return [3 /*break*/, 6]; 125 | case 5: 126 | err_2 = _b.sent(); 127 | throw err_2; 128 | case 6: return [2 /*return*/]; 129 | } 130 | }); 131 | }); 132 | }; 133 | return SiteFields; 134 | }(HandlerBase)); 135 | export { SiteFields }; 136 | -------------------------------------------------------------------------------- /lib/handlers/websettings.d.ts: -------------------------------------------------------------------------------- 1 | import { HandlerBase } from "./handlerbase"; 2 | import { IWebSettings } from "../schema"; 3 | import { Web } from "@pnp/sp"; 4 | import { IProvisioningConfig } from "../provisioningconfig"; 5 | /** 6 | * Describes the WebSettings Object Handler 7 | */ 8 | export declare class WebSettings extends HandlerBase { 9 | /** 10 | * Creates a new instance of the WebSettings class 11 | * 12 | * @param {IProvisioningConfig} config Provisioning config 13 | */ 14 | constructor(config: IProvisioningConfig); 15 | /** 16 | * Provisioning WebSettings 17 | * 18 | * @param {Web} web The web 19 | * @param {IWebSettings} settings The settings 20 | */ 21 | ProvisionObjects(web: Web, settings: IWebSettings): Promise; 22 | } 23 | -------------------------------------------------------------------------------- /lib/handlers/websettings.js: -------------------------------------------------------------------------------- 1 | var __extends = (this && this.__extends) || (function () { 2 | var extendStatics = Object.setPrototypeOf || 3 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 4 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 5 | return function (d, b) { 6 | extendStatics(d, b); 7 | function __() { this.constructor = d; } 8 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 9 | }; 10 | })(); 11 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 12 | return new (P || (P = Promise))(function (resolve, reject) { 13 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 14 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 15 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 16 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 17 | }); 18 | }; 19 | var __generator = (this && this.__generator) || function (thisArg, body) { 20 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 21 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 22 | function verb(n) { return function (v) { return step([n, v]); }; } 23 | function step(op) { 24 | if (f) throw new TypeError("Generator is already executing."); 25 | while (_) try { 26 | if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; 27 | if (y = 0, t) op = [0, t.value]; 28 | switch (op[0]) { 29 | case 0: case 1: t = op; break; 30 | case 4: _.label++; return { value: op[1], done: false }; 31 | case 5: _.label++; y = op[1]; op = [0]; continue; 32 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 33 | default: 34 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 35 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 36 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 37 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 38 | if (t[2]) _.ops.pop(); 39 | _.trys.pop(); continue; 40 | } 41 | op = body.call(thisArg, _); 42 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 43 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 44 | } 45 | }; 46 | import { HandlerBase } from "./handlerbase"; 47 | import * as omit from "object.omit"; 48 | import { replaceUrlTokens } from "../util"; 49 | /** 50 | * Describes the WebSettings Object Handler 51 | */ 52 | var WebSettings = (function (_super) { 53 | __extends(WebSettings, _super); 54 | /** 55 | * Creates a new instance of the WebSettings class 56 | * 57 | * @param {IProvisioningConfig} config Provisioning config 58 | */ 59 | function WebSettings(config) { 60 | return _super.call(this, "WebSettings", config) || this; 61 | } 62 | /** 63 | * Provisioning WebSettings 64 | * 65 | * @param {Web} web The web 66 | * @param {IWebSettings} settings The settings 67 | */ 68 | WebSettings.prototype.ProvisionObjects = function (web, settings) { 69 | return __awaiter(this, void 0, void 0, function () { 70 | var _this = this; 71 | var err_1; 72 | return __generator(this, function (_a) { 73 | switch (_a.label) { 74 | case 0: 75 | _super.prototype.scope_started.call(this); 76 | Object.keys(settings) 77 | .filter(function (key) { return typeof (settings[key]) === "string"; }) 78 | .forEach(function (key) { 79 | var value = settings[key]; 80 | settings[key] = replaceUrlTokens(value, _this.config); 81 | }); 82 | _a.label = 1; 83 | case 1: 84 | _a.trys.push([1, 3, , 4]); 85 | return [4 /*yield*/, Promise.all([ 86 | web.rootFolder.update({ WelcomePage: settings.WelcomePage }), 87 | web.update(omit(settings, "WelcomePage")), 88 | ])]; 89 | case 2: 90 | _a.sent(); 91 | _super.prototype.scope_ended.call(this); 92 | return [3 /*break*/, 4]; 93 | case 3: 94 | err_1 = _a.sent(); 95 | _super.prototype.scope_ended.call(this); 96 | throw err_1; 97 | case 4: return [2 /*return*/]; 98 | } 99 | }); 100 | }); 101 | }; 102 | return WebSettings; 103 | }(HandlerBase)); 104 | export { WebSettings }; 105 | -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | export { Schema } from "./schema"; 2 | export { WebProvisioner } from "./webprovisioner"; 3 | export { Web } from "@pnp/sp"; 4 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | export { WebProvisioner } from "./webprovisioner"; 2 | export { Web } from "@pnp/sp"; 3 | -------------------------------------------------------------------------------- /lib/provisioningconfig.d.ts: -------------------------------------------------------------------------------- 1 | import { LogLevel } from "@pnp/logging"; 2 | export declare type ProvisioningParameters = { 3 | [key: string]: string; 4 | }; 5 | export interface IProvisioningLogging { 6 | activeLogLevel?: LogLevel; 7 | prefix?: string; 8 | } 9 | /** 10 | * Describes the Provisioning Config 11 | */ 12 | export interface IProvisioningConfig { 13 | parameters?: ProvisioningParameters; 14 | spfxContext?: any; 15 | logging?: IProvisioningLogging; 16 | } 17 | -------------------------------------------------------------------------------- /lib/provisioningconfig.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pnp/sp-js-provisioning/2573fcae8ba2669c24cfae532306397c649c8758/lib/provisioningconfig.js -------------------------------------------------------------------------------- /lib/provisioningcontext.d.ts: -------------------------------------------------------------------------------- 1 | import { IContentType } from "./schema"; 2 | /** 3 | * Describes the Provisioning Context 4 | */ 5 | export declare class ProvisioningContext { 6 | web: any; 7 | lists: { 8 | [key: string]: string; 9 | }; 10 | siteFields: { 11 | [key: string]: string; 12 | }; 13 | contentTypes: { 14 | [key: string]: IContentType; 15 | }; 16 | /** 17 | * Creates a new instance of the ProvisioningContext class 18 | */ 19 | constructor(); 20 | } 21 | -------------------------------------------------------------------------------- /lib/provisioningcontext.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Describes the Provisioning Context 3 | */ 4 | var ProvisioningContext = (function () { 5 | /** 6 | * Creates a new instance of the ProvisioningContext class 7 | */ 8 | function ProvisioningContext() { 9 | this.web = null; 10 | this.lists = {}; 11 | this.siteFields = {}; 12 | this.contentTypes = {}; 13 | } 14 | return ProvisioningContext; 15 | }()); 16 | export { ProvisioningContext }; 17 | -------------------------------------------------------------------------------- /lib/schema.d.ts: -------------------------------------------------------------------------------- 1 | import { CanvasColumnFactorType } from "@pnp/sp"; 2 | export interface Schema { 3 | Navigation?: INavigation; 4 | CustomActions?: ICustomAction[]; 5 | ComposedLook?: IComposedLook; 6 | WebSettings?: IWebSettings; 7 | Features?: IFeature[]; 8 | Lists?: IList[]; 9 | Files?: IFile[]; 10 | PropertyBagEntries?: IPropertyBagEntry[]; 11 | ClientSidePages?: IClientSidePage[]; 12 | SiteFields?: string[]; 13 | ContentTypes: IContentType[]; 14 | [key: string]: any; 15 | } 16 | export default Schema; 17 | export interface IFieldRef { 18 | ID: string; 19 | Name?: string; 20 | Required?: boolean; 21 | Hidden?: boolean; 22 | } 23 | export interface IContentType { 24 | ID: string; 25 | Name: string; 26 | Description: string; 27 | Group: string; 28 | FieldRefs: IFieldRef[]; 29 | } 30 | export interface IClientSideControl { 31 | Title: string; 32 | Description?: string; 33 | ClientSideComponentId: string; 34 | ClientSideComponentProperties?: any; 35 | } 36 | export interface IClientSidePageColumn { 37 | Factor: CanvasColumnFactorType; 38 | Controls: IClientSideControl[]; 39 | } 40 | export interface IClientSidePageSection { 41 | Columns: IClientSidePageColumn[]; 42 | } 43 | export interface IClientSidePage { 44 | Name: string; 45 | Title: string; 46 | LibraryTitle?: string; 47 | PageLayoutType?: string; 48 | CommentsDisabled?: boolean; 49 | Sections?: IClientSidePageSection[]; 50 | } 51 | export interface IFeature { 52 | id: string; 53 | deactivate: boolean; 54 | force: boolean; 55 | } 56 | export interface IFile { 57 | Folder: string; 58 | Src: string; 59 | Url: string; 60 | Overwrite?: boolean; 61 | RemoveExistingWebParts?: boolean; 62 | WebParts?: IWebPart[]; 63 | Properties?: { 64 | [key: string]: string | number; 65 | }; 66 | } 67 | export interface IWebPartPropertyOverride { 68 | name: string; 69 | type: string; 70 | value: string; 71 | } 72 | export interface IWebPart { 73 | Title: string; 74 | Zone: string; 75 | Order: number; 76 | Contents: IWebPartContents; 77 | PropertyOverrides?: IWebPartPropertyOverride[]; 78 | ListView?: { 79 | List: string; 80 | View: IListView; 81 | }; 82 | } 83 | export interface IWebPartContents { 84 | Xml?: string; 85 | FileSrc?: string; 86 | } 87 | export interface IComposedLook { 88 | ColorPaletteUrl: string; 89 | FontSchemeUrl: string; 90 | BackgroundImageUrl: string; 91 | } 92 | export interface ICustomAction { 93 | Name: string; 94 | Description?: string; 95 | Title: string; 96 | Location: string; 97 | Url: string; 98 | [key: string]: string; 99 | } 100 | export interface IWebSettings { 101 | WelcomePage?: string; 102 | AlternateCssUrl?: string; 103 | SaveSiteAsTemplateEnabled?: boolean; 104 | MasterUrl?: string; 105 | CustomMasterUrl?: string; 106 | RecycleBinEnabled?: boolean; 107 | TreeViewEnabled?: boolean; 108 | QuickLaunchEnabled?: boolean; 109 | SiteLogoUrl?: string; 110 | [key: string]: string | boolean; 111 | } 112 | export interface INavigation { 113 | QuickLaunch?: INavigationNode[]; 114 | TopNavigationBar?: INavigationNode[]; 115 | } 116 | export interface INavigationNode { 117 | Title: string; 118 | Url: string; 119 | IgnoreExisting?: boolean; 120 | Children?: INavigationNode[]; 121 | } 122 | export interface IRoleAssignment { 123 | Principal: string; 124 | RoleDefinition: string; 125 | } 126 | export interface IListSecurity { 127 | BreakRoleInheritance?: boolean; 128 | CopyRoleAssignments?: boolean; 129 | ClearSubscopes?: boolean; 130 | RoleAssignments?: IRoleAssignment[]; 131 | } 132 | export interface IList { 133 | Title: string; 134 | Description: string; 135 | Template: number; 136 | ContentTypesEnabled: boolean; 137 | RemoveExistingContentTypes?: boolean; 138 | ContentTypeBindings?: IContentTypeBinding[]; 139 | Fields?: string[]; 140 | FieldRefs?: IListInstanceFieldRef[]; 141 | Views?: IListView[]; 142 | Security?: IListSecurity; 143 | AdditionalSettings?: { 144 | DefaultContentApprovalWorkflowId?: string; 145 | DefaultDisplayFormUrl?: string; 146 | DefaultEditFormUrl?: string; 147 | DefaultNewFormUrl?: string; 148 | Description?: string; 149 | Direction?: string; 150 | DocumentTemplateUrl?: string; 151 | /** 152 | * Reader = 0; Author = 1; Approver = 2. 153 | */ 154 | DraftVersionVisibility?: number; 155 | EnableAttachments?: boolean; 156 | EnableFolderCreation?: boolean; 157 | EnableMinorVersions?: boolean; 158 | EnableModeration?: boolean; 159 | EnableVersioning?: boolean; 160 | ForceCheckout?: boolean; 161 | Hidden?: boolean; 162 | IrmEnabled?: boolean; 163 | IrmExpire?: boolean; 164 | IrmReject?: boolean; 165 | IsApplicationList?: boolean; 166 | NoCrawl?: boolean; 167 | OnQuickLaunch?: boolean; 168 | Title?: string; 169 | ValidationFormula?: string; 170 | ValidationMessage?: string; 171 | [key: string]: string | boolean | number; 172 | }; 173 | } 174 | export interface IListInstanceFieldRef extends IFieldRef { 175 | DisplayName?: string; 176 | } 177 | export interface IContentTypeBinding { 178 | ContentTypeID: string; 179 | Name?: string; 180 | } 181 | export interface IListView { 182 | Title: string; 183 | PersonalView?: boolean; 184 | ViewFields?: string[]; 185 | AdditionalSettings?: { 186 | ViewQuery?: string; 187 | RowLimit?: number; 188 | Paged?: boolean; 189 | Hidden?: boolean; 190 | Scope?: 0 | 1; 191 | DefaultView?: boolean; 192 | JSLink?: string; 193 | }; 194 | } 195 | export interface IPropertyBagEntry { 196 | Key: string; 197 | Value: string; 198 | Indexed?: boolean; 199 | Overwrite?: boolean; 200 | } 201 | -------------------------------------------------------------------------------- /lib/schema.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pnp/sp-js-provisioning/2573fcae8ba2669c24cfae532306397c649c8758/lib/schema.js -------------------------------------------------------------------------------- /lib/util/index.d.ts: -------------------------------------------------------------------------------- 1 | import { IProvisioningConfig } from "../provisioningconfig"; 2 | export declare function replaceUrlTokens(str: string, config: IProvisioningConfig): string; 3 | export declare function makeUrlRelative(absUrl: string): string; 4 | export declare function base64EncodeString(str: string): string; 5 | export declare function isNode(): boolean; 6 | -------------------------------------------------------------------------------- /lib/util/index.js: -------------------------------------------------------------------------------- 1 | export function replaceUrlTokens(str, config) { 2 | var siteAbsoluteUrl = null; 3 | if (config.spfxContext) { 4 | siteAbsoluteUrl = config.spfxContext.pageContext.site.absoluteUrl; 5 | } 6 | else if (window.hasOwnProperty("_spPageContextInfo")) { 7 | siteAbsoluteUrl = _spPageContextInfo.siteAbsoluteUrl; 8 | } 9 | return str 10 | .replace(/{sitecollection}/g, siteAbsoluteUrl) 11 | .replace(/{wpgallery}/g, siteAbsoluteUrl + "/_catalogs/wp") 12 | .replace(/{hosturl}/g, window.location.protocol + "//" + window.location.host + ":" + window.location.port) 13 | .replace(/{themegallery}/g, siteAbsoluteUrl + "/_catalogs/theme/15"); 14 | } 15 | export function makeUrlRelative(absUrl) { 16 | return absUrl.replace(document.location.protocol + "//" + document.location.hostname, ""); 17 | } 18 | export function base64EncodeString(str) { 19 | var bytes = []; 20 | for (var i = 0; i < str.length; ++i) { 21 | bytes.push(str.charCodeAt(i)); 22 | bytes.push(0); 23 | } 24 | var b64encoded = window.btoa(String.fromCharCode.apply(null, bytes)); 25 | return b64encoded; 26 | } 27 | export function isNode() { 28 | return typeof window === "undefined"; 29 | } 30 | -------------------------------------------------------------------------------- /lib/util/tokenhelper.d.ts: -------------------------------------------------------------------------------- 1 | import { ProvisioningContext } from "../provisioningcontext"; 2 | import { IProvisioningConfig } from "../provisioningconfig"; 3 | /** 4 | * Describes the Token Helper 5 | */ 6 | export declare class TokenHelper { 7 | context: ProvisioningContext; 8 | config: IProvisioningConfig; 9 | private tokenRegex; 10 | /** 11 | * Creates a new instance of the TokenHelper class 12 | */ 13 | constructor(context: ProvisioningContext, config: IProvisioningConfig); 14 | replaceTokens(str: string): string; 15 | } 16 | -------------------------------------------------------------------------------- /lib/util/tokenhelper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Describes the Token Helper 3 | */ 4 | var TokenHelper = (function () { 5 | /** 6 | * Creates a new instance of the TokenHelper class 7 | */ 8 | function TokenHelper(context, config) { 9 | this.context = context; 10 | this.config = config; 11 | this.tokenRegex = /{[a-z]*:[ÆØÅæøåA-za-z ]*}/g; 12 | } 13 | TokenHelper.prototype.replaceTokens = function (str) { 14 | var _this = this; 15 | var m; 16 | while ((m = this.tokenRegex.exec(str)) !== null) { 17 | if (m.index === this.tokenRegex.lastIndex) { 18 | this.tokenRegex.lastIndex++; 19 | } 20 | m.forEach(function (match) { 21 | var _a = match.replace(/[\{\}]/g, "").split(":"), tokenKey = _a[0], tokenValue = _a[1]; 22 | switch (tokenKey) { 23 | case "listid": 24 | { 25 | if (_this.context.lists[tokenValue]) { 26 | str = str.replace(match, _this.context.lists[tokenValue]); 27 | } 28 | } 29 | break; 30 | case "siteid": 31 | { 32 | if (_this.context.web.Id) { 33 | str = str.replace(match, _this.context.web.Id); 34 | } 35 | } 36 | break; 37 | case "parameter": 38 | { 39 | if (_this.config.parameters) { 40 | var paramValue = _this.config.parameters[tokenValue]; 41 | if (paramValue) { 42 | str = str.replace(match, paramValue); 43 | } 44 | } 45 | } 46 | break; 47 | } 48 | }); 49 | } 50 | return str; 51 | }; 52 | return TokenHelper; 53 | }()); 54 | export { TokenHelper }; 55 | -------------------------------------------------------------------------------- /lib/webprovisioner.d.ts: -------------------------------------------------------------------------------- 1 | import { Schema } from "./schema"; 2 | import { HandlerBase } from "./handlers/handlerbase"; 3 | import { Web } from "@pnp/sp"; 4 | import { TypedHash } from "@pnp/common"; 5 | import { IProvisioningConfig } from "./provisioningconfig"; 6 | /** 7 | * Root class of Provisioning 8 | */ 9 | export declare class WebProvisioner { 10 | private web; 11 | handlerSort: TypedHash; 12 | handlerMap: TypedHash; 13 | private context; 14 | private config; 15 | /** 16 | * Creates a new instance of the Provisioner class 17 | * 18 | * @param {Web} web The Web instance to which we want to apply templates 19 | * @param {TypedHash} handlermap A set of handlers we want to apply. The keys of the map need to match the property names in the template 20 | */ 21 | constructor(web: Web, handlerSort?: TypedHash); 22 | private onSetup(); 23 | /** 24 | * Applies the supplied template to the web used to create this Provisioner instance 25 | * 26 | * @param {Schema} template The template to apply 27 | * @param {string[]} handlers A set of handlers we want to apply 28 | * @param {Function} progressCallback Callback for progress updates 29 | */ 30 | applyTemplate(template: Schema, handlers: string[], progressCallback?: (msg: string) => void): Promise; 31 | /** 32 | * Sets up the web provisioner 33 | * 34 | * @param {IProvisioningConfig} config Provisioning config 35 | */ 36 | setup(config: IProvisioningConfig): void; 37 | } 38 | -------------------------------------------------------------------------------- /lib/webprovisioner.js: -------------------------------------------------------------------------------- 1 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 2 | return new (P || (P = Promise))(function (resolve, reject) { 3 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 4 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 5 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 6 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 7 | }); 8 | }; 9 | var __generator = (this && this.__generator) || function (thisArg, body) { 10 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 11 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 12 | function verb(n) { return function (v) { return step([n, v]); }; } 13 | function step(op) { 14 | if (f) throw new TypeError("Generator is already executing."); 15 | while (_) try { 16 | if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; 17 | if (y = 0, t) op = [0, t.value]; 18 | switch (op[0]) { 19 | case 0: case 1: t = op; break; 20 | case 4: _.label++; return { value: op[1], done: false }; 21 | case 5: _.label++; y = op[1]; op = [0]; continue; 22 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 23 | default: 24 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 25 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 26 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 27 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 28 | if (t[2]) _.ops.pop(); 29 | _.trys.pop(); continue; 30 | } 31 | op = body.call(thisArg, _); 32 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 33 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 34 | } 35 | }; 36 | import { Logger, ConsoleListener } from "@pnp/logging"; 37 | import { DefaultHandlerMap, DefaultHandlerSort } from "./handlers/exports"; 38 | import { ProvisioningContext } from "./provisioningcontext"; 39 | /** 40 | * Root class of Provisioning 41 | */ 42 | var WebProvisioner = (function () { 43 | /** 44 | * Creates a new instance of the Provisioner class 45 | * 46 | * @param {Web} web The Web instance to which we want to apply templates 47 | * @param {TypedHash} handlermap A set of handlers we want to apply. The keys of the map need to match the property names in the template 48 | */ 49 | function WebProvisioner(web, handlerSort) { 50 | if (handlerSort === void 0) { handlerSort = DefaultHandlerSort; } 51 | this.web = web; 52 | this.handlerSort = handlerSort; 53 | this.context = new ProvisioningContext(); 54 | } 55 | WebProvisioner.prototype.onSetup = function () { 56 | return __awaiter(this, void 0, void 0, function () { 57 | var _a; 58 | return __generator(this, function (_b) { 59 | switch (_b.label) { 60 | case 0: 61 | if (this.config && this.config.logging) { 62 | Logger.subscribe(new ConsoleListener()); 63 | Logger.activeLogLevel = this.config.logging.activeLogLevel; 64 | } 65 | this.handlerMap = DefaultHandlerMap(this.config); 66 | _a = this.context; 67 | return [4 /*yield*/, this.web.get()]; 68 | case 1: 69 | _a.web = _b.sent(); 70 | return [2 /*return*/]; 71 | } 72 | }); 73 | }); 74 | }; 75 | /** 76 | * Applies the supplied template to the web used to create this Provisioner instance 77 | * 78 | * @param {Schema} template The template to apply 79 | * @param {string[]} handlers A set of handlers we want to apply 80 | * @param {Function} progressCallback Callback for progress updates 81 | */ 82 | WebProvisioner.prototype.applyTemplate = function (template, handlers, progressCallback) { 83 | return __awaiter(this, void 0, void 0, function () { 84 | var _this = this; 85 | var operations, error_1; 86 | return __generator(this, function (_a) { 87 | switch (_a.label) { 88 | case 0: return [4 /*yield*/, this.onSetup()]; 89 | case 1: 90 | _a.sent(); 91 | operations = Object.getOwnPropertyNames(template) 92 | .sort(function (name1, name2) { 93 | var sort1 = _this.handlerSort.hasOwnProperty(name1) ? _this.handlerSort[name1] : 99; 94 | var sort2 = _this.handlerSort.hasOwnProperty(name2) ? _this.handlerSort[name2] : 99; 95 | return sort1 - sort2; 96 | }); 97 | if (handlers) { 98 | operations = operations.filter(function (op) { return handlers.indexOf(op) !== -1; }); 99 | } 100 | _a.label = 2; 101 | case 2: 102 | _a.trys.push([2, 4, , 5]); 103 | return [4 /*yield*/, operations.reduce(function (chain, name) { 104 | var handler = _this.handlerMap[name]; 105 | return chain.then(function () { 106 | if (progressCallback) { 107 | progressCallback(name); 108 | } 109 | return handler.ProvisionObjects(_this.web, template[name], _this.context); 110 | }); 111 | }, Promise.resolve())]; 112 | case 3: 113 | _a.sent(); 114 | return [3 /*break*/, 5]; 115 | case 4: 116 | error_1 = _a.sent(); 117 | throw error_1; 118 | case 5: return [2 /*return*/]; 119 | } 120 | }); 121 | }); 122 | }; 123 | /** 124 | * Sets up the web provisioner 125 | * 126 | * @param {IProvisioningConfig} config Provisioning config 127 | */ 128 | WebProvisioner.prototype.setup = function (config) { 129 | this.config = config; 130 | }; 131 | return WebProvisioner; 132 | }()); 133 | export { WebProvisioner }; 134 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sp-js-provisioning", 3 | "version": "0.9.26", 4 | "description": "A reusable JavaScript library targeting SharePoint provisioning.", 5 | "main": "./lib/index.js", 6 | "typings": "./lib/index.d.ts", 7 | "dependencies": { 8 | "@pnp/common": "^1.2.7", 9 | "@pnp/logging": "^1.2.7", 10 | "@pnp/nodejs": "^1.2.3", 11 | "@pnp/odata": "^1.2.7", 12 | "@pnp/sp": "^1.2.7", 13 | "gulp-typescript": "^3.2.2", 14 | "object.omit": "^3.0.0", 15 | "spfx-jsom": "^0.6.0", 16 | "xml-js": "^1.4.2" 17 | }, 18 | "devDependencies": { 19 | "@microsoft/sp-tslint-rules": "^1.7.1", 20 | "@types/chai": "^4.0.4", 21 | "@types/chai-as-promised": "^7.1.0", 22 | "@types/es6-promise": "0.0.33", 23 | "@types/mocha": "^2.2.32", 24 | "@types/node": "^8.0.28", 25 | "@types/sharepoint": "^2016.1.2", 26 | "@types/webpack-env": "1.13.1", 27 | "babel-core": "6.26.0", 28 | "babel-loader": "^7.1.2", 29 | "babel-preset-es2015": "6.24.1", 30 | "chai": "4.1.2", 31 | "chai-as-promised": "7.1.1", 32 | "del": "3.0.0", 33 | "gulp": "3.9.1", 34 | "gulp-if": "2.0.2", 35 | "gulp-istanbul": "1.1.2", 36 | "gulp-jsdoc3": "^1.0.1", 37 | "gulp-mocha": "^4.3.1", 38 | "gulp-sourcemaps": "2.6.1", 39 | "gulp-tslint": "8.1.2", 40 | "gulp-util": "3.0.8", 41 | "gulp-watch": "^5.0.1", 42 | "jsdoc": "3.5.5", 43 | "merge": "1.2.0", 44 | "merge2": "^1.0.2", 45 | "mocha": "3.5.3", 46 | "node-fetch": "1.7.3", 47 | "run-sequence": "^2.2.1", 48 | "semver": "5.4.1", 49 | "ts-loader": "^2.3.7", 50 | "tslint": "^5.9.1", 51 | "tslint-microsoft-contrib": "^6.0.0", 52 | "typescript": "2.4.2", 53 | "webpack": "3.6.0", 54 | "yargs": "9.0.1" 55 | }, 56 | "scripts": { 57 | "test": "gulp test" 58 | }, 59 | "repository": { 60 | "type": "git", 61 | "url": "git://github.com/sharepoint/pnp-js-provisioning" 62 | }, 63 | "author": { 64 | "name": "Microsoft and other contributors" 65 | }, 66 | "license": "MIT", 67 | "keywords": [ 68 | "sharepoint", 69 | "office365", 70 | "tools", 71 | "spfx", 72 | "sharepoint framework", 73 | "sharepoint provisioning", 74 | "PnP", 75 | "Patterns & Practices" 76 | ], 77 | "bugs": { 78 | "url": "https://github.com/pnp/sp-js-provisioning/issues" 79 | }, 80 | "homepage": "https://github.com/pnp/sp-js-provisioning" 81 | } 82 | -------------------------------------------------------------------------------- /sample-schemas/all-simple.ts: -------------------------------------------------------------------------------- 1 | import Schema from "../src/schema"; 2 | 3 | let template: Schema = { 4 | 5 | CustomActions: [{ 6 | Name: "Test", 7 | Title: "Test Title", 8 | Location: "EditControlBlock", 9 | Url: "https://someurl" 10 | }], 11 | 12 | Features: [{ 13 | id: "87294c72-f260-42f3-a41b-981a2ffce37a", 14 | deactivate: true, 15 | force: false 16 | }], 17 | 18 | WebSettings: { 19 | TreeViewEnabled: true 20 | }, 21 | 22 | Navigation: { 23 | QuickLaunch: [ 24 | { 25 | Title: "First One", 26 | Url: "https://bing.com" 27 | }, 28 | { 29 | Title: "Second One", 30 | Url: "https://bing.com", 31 | Children: [{ 32 | Title: "Child - 1", 33 | Url: "https://bing.com" 34 | }, 35 | { 36 | Title: "Child - 2", 37 | Url: "https://bing.com" 38 | }] 39 | } 40 | ], 41 | TopNavigationBar: [ 42 | { 43 | Title: "First One", 44 | Url: "https://bing.com" 45 | }, 46 | { 47 | Title: "Second One", 48 | Url: "https://bing.com", 49 | Children: [{ 50 | Title: "Child!", 51 | Url: "https://bing.com" 52 | }] 53 | } 54 | ] 55 | }, 56 | 57 | Lists: [{ 58 | Title: "Prov List 1", 59 | ContentTypesEnabled: true, 60 | Description: "This is the description", 61 | Template: 100 62 | }, 63 | { 64 | Title: "Documents", 65 | ContentTypesEnabled: true, 66 | Description: "This is the description", 67 | Template: 101, 68 | AdditionalSettings: { 69 | Description: "I've updated the description", 70 | EnableFolderCreation: true, 71 | ForceCheckout: true 72 | } 73 | }] 74 | }; 75 | 76 | export default template; -------------------------------------------------------------------------------- /settings.example.js: -------------------------------------------------------------------------------- 1 | var settings = { 2 | 3 | spsave: { 4 | username: "develina.devsson@mydevtenant.onmicrosoft.com", 5 | password: "pass@word1", 6 | siteUrl: "https://mydevtenant.sharepoint.com/" 7 | }, 8 | testing: { 9 | clientId: "{ client id }", 10 | clientSecret: "{ client secret }", 11 | enableWebTests: false, 12 | siteUrl: "{ site collection url }", 13 | notificationUrl: "{ notification url }", 14 | } 15 | } 16 | 17 | module.exports = settings; -------------------------------------------------------------------------------- /src/handlers/clientsidepages.ts: -------------------------------------------------------------------------------- 1 | import { IClientSidePage } from "../schema"; 2 | import { HandlerBase } from "./handlerbase"; 3 | import { Web, ClientSideWebpart } from "@pnp/sp"; 4 | import { ProvisioningContext } from "../provisioningcontext"; 5 | import { IProvisioningConfig } from "../provisioningconfig"; 6 | import { TokenHelper } from '../util/tokenhelper'; 7 | 8 | /** 9 | * Describes the Composed Look Object Handler 10 | */ 11 | export class ClientSidePages extends HandlerBase { 12 | private tokenHelper: TokenHelper; 13 | 14 | /** 15 | * Creates a new instance of the ObjectClientSidePages class 16 | */ 17 | constructor(config: IProvisioningConfig) { 18 | super("ClientSidePages", config); 19 | } 20 | 21 | /** 22 | * Provisioning Client Side Pages 23 | * 24 | * @param {Web} web The web 25 | * @param {IClientSidePage[]} clientSidePages The client side pages to provision 26 | * @param {ProvisioningContext} context Provisioning context 27 | */ 28 | public async ProvisionObjects(web: Web, clientSidePages: IClientSidePage[], context: ProvisioningContext): Promise { 29 | this.tokenHelper = new TokenHelper(context, this.config); 30 | super.scope_started(); 31 | try { 32 | await clientSidePages.reduce((chain: any, clientSidePage) => chain.then(() => this.processClientSidePage(web, clientSidePage)), Promise.resolve()); 33 | } catch (err) { 34 | super.scope_ended(); 35 | throw err; 36 | } 37 | } 38 | 39 | /** 40 | * Provision a client side page 41 | * 42 | * @param {Web} web The web 43 | * @param {IClientSidePage} clientSidePage Cient side page 44 | */ 45 | private async processClientSidePage(web: Web, clientSidePage: IClientSidePage) { 46 | super.log_info("processClientSidePage", `Processing client side page ${clientSidePage.Name}`); 47 | const page = await web.addClientSidePage(clientSidePage.Name, clientSidePage.Title, clientSidePage.LibraryTitle); 48 | if (clientSidePage.Sections) { 49 | clientSidePage.Sections.forEach(s => { 50 | const section = page.addSection(); 51 | s.Columns.forEach(col => { 52 | const column = section.addColumn(col.Factor); 53 | col.Controls.forEach(control => { 54 | let controlJsonString = this.tokenHelper.replaceTokens(JSON.stringify(control)); 55 | control = JSON.parse(controlJsonString); 56 | super.log_info("processClientSidePage", `Adding ${control.Title} to client side page ${clientSidePage.Name}`); 57 | column.addControl(new ClientSideWebpart(control.Title, control.Description, control.ClientSideComponentProperties, control.ClientSideComponentId)); 58 | }); 59 | }); 60 | }); 61 | await page.save(); 62 | } 63 | await page.publish(); 64 | if (clientSidePage.CommentsDisabled) { 65 | super.log_info("processClientSidePage", `Disabling comments for client side page ${clientSidePage.Name}`); 66 | await page.disableComments(); 67 | } 68 | if (clientSidePage.PageLayoutType) { 69 | super.log_info("processClientSidePage", `Setting page layout ${clientSidePage.PageLayoutType} for client side page ${clientSidePage.Name}`); 70 | const pageItem = await page.getItem(); 71 | await pageItem.update({ PageLayoutType: clientSidePage.PageLayoutType }); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/handlers/composedlook.ts: -------------------------------------------------------------------------------- 1 | import { IComposedLook } from "../schema"; 2 | import { HandlerBase } from "./handlerbase"; 3 | import { Web } from "@pnp/sp"; 4 | import { replaceUrlTokens, makeUrlRelative } from "../util"; 5 | import { IProvisioningConfig} from "../provisioningconfig"; 6 | 7 | /** 8 | * Describes the Composed Look Object Handler 9 | */ 10 | export class ComposedLook extends HandlerBase { 11 | /** 12 | * Creates a new instance of the ObjectComposedLook class 13 | */ 14 | constructor(config: IProvisioningConfig) { 15 | super("ComposedLook", config); 16 | } 17 | 18 | /** 19 | * Provisioning Composed Look 20 | * 21 | * @param {Web} web The web 22 | * @param {IComposedLook} object The Composed Look to provision 23 | */ 24 | public async ProvisionObjects(web: Web, composedLook: IComposedLook): Promise { 25 | super.scope_started(); 26 | try { 27 | await web.applyTheme( 28 | makeUrlRelative(replaceUrlTokens(composedLook.ColorPaletteUrl, this.config)), 29 | makeUrlRelative(replaceUrlTokens(composedLook.FontSchemeUrl, this.config)), 30 | composedLook.BackgroundImageUrl ? makeUrlRelative(replaceUrlTokens(composedLook.BackgroundImageUrl, this.config)) : null, 31 | false); 32 | super.scope_ended(); 33 | } catch (err) { 34 | super.scope_ended(); 35 | throw err; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/handlers/contenttypes.ts: -------------------------------------------------------------------------------- 1 | import initSpfxJsom, { ExecuteJsomQuery, JsomContext } from "spfx-jsom"; 2 | import { HandlerBase } from "./handlerbase"; 3 | import { Web, ContentTypeAddResult } from "@pnp/sp"; 4 | import { ProvisioningContext } from "../provisioningcontext"; 5 | import { IProvisioningConfig } from "../provisioningconfig"; 6 | import { TokenHelper } from '../util/tokenhelper'; 7 | import { IContentType } from '../schema'; 8 | 9 | /** 10 | * Describes the Content Types Object Handler 11 | */ 12 | export class ContentTypes extends HandlerBase { 13 | private jsomContext: JsomContext; 14 | private context: ProvisioningContext; 15 | 16 | /** 17 | * Creates a new instance of the ObjectSiteFields class 18 | */ 19 | constructor(config: IProvisioningConfig) { 20 | super("ContentTypes", config); 21 | } 22 | 23 | /** 24 | * Provisioning Content Types 25 | * 26 | * @param {Web} web The web 27 | * @param {IContentType[]} contentTypes The content types 28 | * @param {ProvisioningContext} context Provisioning context 29 | */ 30 | public async ProvisionObjects(web: Web, contentTypes: IContentType[], context: ProvisioningContext): Promise { 31 | this.jsomContext = await initSpfxJsom(context.web.ServerRelativeUrl); 32 | this.context = context; 33 | super.scope_started(); 34 | try { 35 | this.context.contentTypes = (await web.contentTypes.select('Id', 'Name', 'FieldLinks').expand('FieldLinks').get>()).reduce((obj, contentType) => { 36 | obj[contentType.Name] = { 37 | ID: contentType.Id.StringValue, 38 | Name: contentType.Name, 39 | FieldRefs: contentType.FieldLinks.map(fl => ({ 40 | ID: fl.Id, 41 | Name: fl.Name, 42 | Required: fl.Required, 43 | Hidden: fl.Hidden, 44 | })), 45 | }; 46 | return obj; 47 | }, {}); 48 | await contentTypes 49 | .sort((a, b) => { 50 | if (a.ID < b.ID) { return -1; } 51 | if (a.ID > b.ID) { return 1; } 52 | return 0; 53 | }) 54 | .reduce((chain: any, contentType) => chain.then(() => this.processContentType(web, contentType)), Promise.resolve()); 55 | } catch (err) { 56 | super.scope_ended(); 57 | throw err; 58 | } 59 | } 60 | 61 | /** 62 | * Provision a content type 63 | * 64 | * @param {Web} web The web 65 | * @param {IContentType} contentType Content type 66 | */ 67 | private async processContentType(web: Web, contentType: IContentType): Promise { 68 | try { 69 | let contentTypeId = this.context.contentTypes[contentType.Name].ID; 70 | if (!contentTypeId) { 71 | const contentTypeAddResult = await this.addContentType(web, contentType); 72 | contentTypeId = contentTypeAddResult.data.Id; 73 | } 74 | super.log_info("processContentType", `Processing content type [${contentType.Name}] (${contentTypeId})`); 75 | const spContentType = this.jsomContext.web.get_contentTypes().getById(contentTypeId); 76 | if (contentType.Description) { 77 | spContentType.set_description(contentType.Description); 78 | } 79 | if (contentType.Group) { 80 | spContentType.set_group(contentType.Group); 81 | } 82 | spContentType.update(true); 83 | await ExecuteJsomQuery(this.jsomContext); 84 | if (contentType.FieldRefs) { 85 | await this.processContentTypeFieldRefs(contentType, spContentType); 86 | } 87 | } catch (err) { 88 | throw err; 89 | } 90 | } 91 | 92 | /** 93 | * Add a content type 94 | * 95 | * @param {Web} web The web 96 | * @param {IContentType} contentType Content type 97 | */ 98 | private async addContentType(web: Web, contentType: IContentType): Promise { 99 | try { 100 | super.log_info("addContentType", `Adding content type [${contentType.Name}] (${contentType.ID})`); 101 | return await web.contentTypes.add(contentType.ID, contentType.Name, contentType.Description, contentType.Group); 102 | } catch (err) { 103 | throw err; 104 | } 105 | } 106 | 107 | /** 108 | * Adding content type field refs 109 | * 110 | * @param {IContentType} contentType Content type 111 | * @param {SP.ContentType} spContentType Content type 112 | */ 113 | private async processContentTypeFieldRefs(contentType: IContentType, spContentType: SP.ContentType): Promise { 114 | try { 115 | for (let i = 0; i < contentType.FieldRefs.length; i++) { 116 | const fieldRef = contentType.FieldRefs[i]; 117 | let [existingFieldLink] = this.context.contentTypes[contentType.Name].FieldRefs.filter(fr => fr.Name === fieldRef.Name); 118 | let fieldLink: SP.FieldLink; 119 | if (existingFieldLink) { 120 | fieldLink = spContentType.get_fieldLinks().getById(new SP.Guid(existingFieldLink.ID)); 121 | } else { 122 | super.log_info("processContentTypeFieldRefs", `Adding field ref ${fieldRef.Name} to content type [${contentType.Name}] (${contentType.ID})`); 123 | const siteField = this.jsomContext.web.get_fields().getByInternalNameOrTitle(fieldRef.Name); 124 | const fieldLinkCreationInformation = new SP.FieldLinkCreationInformation(); 125 | fieldLinkCreationInformation.set_field(siteField); 126 | fieldLink = spContentType.get_fieldLinks().add(fieldLinkCreationInformation); 127 | } 128 | if (contentType.FieldRefs[i].hasOwnProperty("Required")) { 129 | fieldLink.set_required(contentType.FieldRefs[i].Required); 130 | } 131 | if (contentType.FieldRefs[i].hasOwnProperty("Hidden")) { 132 | fieldLink.set_hidden(contentType.FieldRefs[i].Hidden); 133 | } 134 | } 135 | spContentType.update(true); 136 | await ExecuteJsomQuery(this.jsomContext); 137 | super.log_info("processContentTypeFieldRefs", `Successfully processed field refs for content type [${contentType.Name}] (${contentType.ID})`); 138 | } catch (error) { 139 | super.log_info("processContentTypeFieldRefs", `Failed to process field refs for content type [${contentType.Name}] (${contentType.ID})`, { error: error.args && error.args.get_message() }); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/handlers/customactions.ts: -------------------------------------------------------------------------------- 1 | import { HandlerBase } from "./handlerbase"; 2 | import { ICustomAction } from "../schema"; 3 | import { Web } from "@pnp/sp"; 4 | import { IProvisioningConfig} from "../provisioningconfig"; 5 | 6 | /** 7 | * Describes the Custom Actions Object Handler 8 | */ 9 | export class CustomActions extends HandlerBase { 10 | /** 11 | * Creates a new instance of the ObjectCustomActions class 12 | * 13 | * @param {IProvisioningConfig} config Provisioning config 14 | */ 15 | constructor(config: IProvisioningConfig) { 16 | super("CustomActions", config); 17 | } 18 | 19 | /** 20 | * Provisioning Custom Actions 21 | * 22 | * @param {Web} web The web 23 | * @param {Array} customactions The Custom Actions to provision 24 | */ 25 | public async ProvisionObjects(web: Web, customActions: ICustomAction[]): Promise { 26 | super.scope_started(); 27 | try { 28 | const existingActions = await web.userCustomActions.select("Title").get<{ Title: string }[]>(); 29 | 30 | let batch = web.createBatch(); 31 | 32 | customActions 33 | .filter(action => { 34 | return !existingActions.some(existingAction => existingAction.Title === action.Title); 35 | }) 36 | .map(action => { 37 | web.userCustomActions.inBatch(batch).add(action); 38 | }); 39 | 40 | await batch.execute(); 41 | super.scope_ended(); 42 | } catch (err) { 43 | super.scope_ended(); 44 | throw err; 45 | } 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /src/handlers/exports.ts: -------------------------------------------------------------------------------- 1 | import { TypedHash } from "@pnp/common"; 2 | import { HandlerBase } from "./handlerbase"; 3 | import { ComposedLook } from "./composedlook"; 4 | import { CustomActions } from "./customactions"; 5 | import { Features } from "./features"; 6 | import { WebSettings } from "./websettings"; 7 | import { Navigation } from "./navigation"; 8 | import { Lists } from "./lists"; 9 | import { Files } from "./files"; 10 | import { ClientSidePages } from "./clientsidepages"; 11 | import { PropertyBagEntries } from "./propertybagentries"; 12 | import { IProvisioningConfig} from "../provisioningconfig"; 13 | import { SiteFields } from "./sitefields"; 14 | import { ContentTypes } from "./contenttypes"; 15 | 16 | export const DefaultHandlerMap = (config: IProvisioningConfig): TypedHash => ({ 17 | ClientSidePages: new ClientSidePages(config), 18 | ComposedLook: new ComposedLook(config), 19 | ContentTypes: new ContentTypes(config), 20 | CustomActions: new CustomActions(config), 21 | Features: new Features(config), 22 | Files: new Files(config), 23 | Lists: new Lists(config), 24 | Navigation: new Navigation(config), 25 | PropertyBagEntries: new PropertyBagEntries(config), 26 | WebSettings: new WebSettings(config), 27 | SiteFields: new SiteFields(config), 28 | }); 29 | 30 | export const DefaultHandlerSort: TypedHash = { 31 | ClientSidePages: 7, 32 | ComposedLook: 6, 33 | ContentTypes: 1, 34 | CustomActions: 5, 35 | Features: 2, 36 | Files: 4, 37 | Lists: 3, 38 | Navigation: 9, 39 | PropertyBagEntries: 8, 40 | WebSettings: 10, 41 | SiteFields: 0, 42 | }; 43 | 44 | -------------------------------------------------------------------------------- /src/handlers/features.ts: -------------------------------------------------------------------------------- 1 | import { HandlerBase } from "./handlerbase"; 2 | import { IFeature } from "../schema"; 3 | import { Web } from "@pnp/sp"; 4 | import { IProvisioningConfig} from "../provisioningconfig"; 5 | 6 | /** 7 | * Describes the Features Object Handler 8 | */ 9 | export class Features extends HandlerBase { 10 | /** 11 | * Creates a new instance of the ObjectFeatures class 12 | * 13 | * @param {IProvisioningConfig} config Provisioning config 14 | */ 15 | constructor(config: IProvisioningConfig) { 16 | super("Features", config); 17 | } 18 | 19 | /** 20 | * Provisioning features 21 | * 22 | * @param {Web} web The web 23 | * @param {Array} features The features to provision 24 | */ 25 | public async ProvisionObjects(web: Web, features: IFeature[]): Promise { 26 | super.scope_started(); 27 | try { 28 | await features.reduce((chain, feature) => { 29 | if (feature.deactivate) { 30 | return chain.then(() => web.features.remove(feature.id, feature.force)); 31 | } else { 32 | return chain.then(() => web.features.add(feature.id, feature.force)); 33 | } 34 | }, Promise.resolve({})); 35 | super.scope_ended(); 36 | } catch (err) { 37 | super.scope_ended(); 38 | throw err; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/handlers/handlerbase.ts: -------------------------------------------------------------------------------- 1 | import { Web } from "@pnp/sp"; 2 | import { Logger, LogLevel } from "@pnp/logging"; 3 | import { ProvisioningContext } from "../provisioningcontext"; 4 | import { IProvisioningConfig } from "../provisioningconfig"; 5 | 6 | /** 7 | * Describes the Object Handler Base 8 | */ 9 | export class HandlerBase { 10 | public config: IProvisioningConfig = {}; 11 | private name: string; 12 | 13 | /** 14 | * Creates a new instance of the ObjectHandlerBase class 15 | * 16 | * @param {string} name Name 17 | * @param {IProvisioningConfig} config Config 18 | */ 19 | constructor(name: string, config: IProvisioningConfig = {}) { 20 | this.name = name; 21 | this.config = config; 22 | } 23 | 24 | /** 25 | * Provisioning objects 26 | */ 27 | public ProvisionObjects(web: Web, templatePart: any, _context?: ProvisioningContext): Promise { 28 | Logger.log({ data: templatePart, level: LogLevel.Warning, message: `Handler ${this.name} for web [${web.toUrl()}] does not override ProvisionObjects.` }); 29 | return Promise.resolve(); 30 | } 31 | 32 | /** 33 | * Writes to Logger when scope has started 34 | */ 35 | public scope_started() { 36 | this.log_info("ProvisionObjects", "Code execution scope started"); 37 | } 38 | 39 | /** 40 | * Writes to Logger when scope has stopped 41 | */ 42 | public scope_ended() { 43 | this.log_info("ProvisionObjects", "Code execution scope ended"); 44 | } 45 | 46 | /** 47 | * Writes to Logger 48 | * 49 | * @param {string} scope Scope 50 | * @param {string} message Message 51 | * @param {any} data Data 52 | */ 53 | public log_info(scope: string, message: string, data?: any) { 54 | let prefix = (this.config.logging && this.config.logging.prefix) ? `${this.config.logging.prefix} ` : ""; 55 | Logger.log({ message: `${prefix}(${this.name}): ${scope}: ${message}`, data: data, level: LogLevel.Info }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/handlers/lists.ts: -------------------------------------------------------------------------------- 1 | import * as xmljs from 'xml-js'; 2 | import { HandlerBase } from './handlerbase'; 3 | import { IContentTypeBinding, IList, IListInstanceFieldRef, IListView } from '../schema'; 4 | import { Web, List } from '@pnp/sp'; 5 | import { ProvisioningContext } from '../provisioningcontext'; 6 | import { IProvisioningConfig } from '../provisioningconfig'; 7 | import { TokenHelper } from '../util/tokenhelper'; 8 | 9 | /** 10 | * Describes the Lists Object Handler 11 | */ 12 | export class Lists extends HandlerBase { 13 | private tokenHelper: TokenHelper; 14 | private context: ProvisioningContext; 15 | 16 | /** 17 | * Creates a new instance of the Lists class 18 | * 19 | * @param {IProvisioningConfig} config Provisioning config 20 | */ 21 | constructor(config: IProvisioningConfig) { 22 | super('Lists', config); 23 | } 24 | 25 | /** 26 | * Provisioning lists 27 | * 28 | * @param {Web} web The web 29 | * @param {Array} lists The lists to provision 30 | */ 31 | public async ProvisionObjects(web: Web, lists: IList[], context: ProvisioningContext): Promise { 32 | this.context = context; 33 | this.tokenHelper = new TokenHelper(this.context, this.config); 34 | super.scope_started(); 35 | try { 36 | await lists.reduce((chain: any, list) => chain.then(() => this.processList(web, list)), Promise.resolve()); 37 | await lists.reduce((chain: any, list) => chain.then(() => this.processListFields(web, list)), Promise.resolve()); 38 | await lists.reduce((chain: any, list) => chain.then(() => this.processListFieldRefs(web, list)), Promise.resolve()); 39 | await lists.reduce((chain: any, list) => chain.then(() => this.processListViews(web, list)), Promise.resolve()); 40 | this.context.lists = (await web.lists.select('Id', 'Title').get>()).reduce((obj, l) => { 41 | obj[l.Title] = l.Id; 42 | return obj; 43 | }, {}); 44 | super.scope_ended(); 45 | } catch (err) { 46 | super.scope_ended(); 47 | throw err; 48 | } 49 | } 50 | 51 | /** 52 | * Processes a list 53 | * 54 | * @param {Web} web The web 55 | * @param {IList} lc The list 56 | */ 57 | private async processList(web: Web, lc: IList): Promise { 58 | super.log_info('processList', `Processing list ${lc.Title}`); 59 | const listEnsureResult = await web.lists.ensure(lc.Title, lc.Description, lc.Template, lc.ContentTypesEnabled, lc.AdditionalSettings); 60 | if (lc.ContentTypeBindings) { 61 | await this.processContentTypeBindings(lc, listEnsureResult.list, lc.ContentTypeBindings, lc.RemoveExistingContentTypes); 62 | } 63 | } 64 | 65 | /** 66 | * Processes content type bindings for a list 67 | * 68 | * @param {IList} lc The list configuration 69 | * @param {List} list The pnp list 70 | * @param {Array} contentTypeBindings Content type bindings 71 | * @param {boolean} removeExisting Remove existing content type bindings 72 | */ 73 | private async processContentTypeBindings(lc: IList, list: List, contentTypeBindings: IContentTypeBinding[], removeExisting: boolean): Promise { 74 | await contentTypeBindings.reduce((chain, ct) => chain.then(() => this.processContentTypeBinding(lc, list, ct.ContentTypeID)), Promise.resolve()); 75 | if (removeExisting) { 76 | let promises = []; 77 | const contentTypes = await list.contentTypes.get(); 78 | contentTypes.forEach(({ Id: { StringValue: ContentTypeId } }) => { 79 | let shouldRemove = (contentTypeBindings.filter(ctb => ContentTypeId.indexOf(ctb.ContentTypeID) !== -1).length === 0) 80 | && (ContentTypeId.indexOf('0x0120') === -1); 81 | if (shouldRemove) { 82 | super.log_info('processContentTypeBindings', `Removing content type ${ContentTypeId} from list ${lc.Title}`); 83 | promises.push(list.contentTypes.getById(ContentTypeId).delete()); 84 | } 85 | }); 86 | await Promise.all(promises); 87 | } 88 | } 89 | 90 | /** 91 | * Processes a content type binding for a list 92 | * 93 | * @param {IList} lc The list configuration 94 | * @param {List} list The pnp list 95 | * @param {string} contentTypeID The Content Type ID 96 | */ 97 | private async processContentTypeBinding(lc: IList, list: List, contentTypeID: string): Promise { 98 | try { 99 | await list.contentTypes.addAvailableContentType(contentTypeID); 100 | super.log_info('processContentTypeBinding', `Content Type ${contentTypeID} added successfully to list ${lc.Title}.`); 101 | } catch (err) { 102 | super.log_info('processContentTypeBinding', `Failed to add Content Type ${contentTypeID} to list ${lc.Title}.`); 103 | } 104 | } 105 | 106 | 107 | /** 108 | * Processes fields for a list 109 | * 110 | * @param {Web} web The web 111 | * @param {IList} list The pnp list 112 | */ 113 | private async processListFields(web: Web, list: IList): Promise { 114 | if (list.Fields) { 115 | await list.Fields.reduce((chain, field) => chain.then(() => this.processField(web, list, field)), Promise.resolve()); 116 | } 117 | } 118 | 119 | /** 120 | * Processes a field for a lit 121 | * 122 | * @param {Web} web The web 123 | * @param {IList} lc The list configuration 124 | * @param {string} fieldXml Field xml 125 | */ 126 | private async processField(web: Web, lc: IList, fieldXml: string): Promise { 127 | const list = web.lists.getByTitle(lc.Title); 128 | const fXmlJson = JSON.parse(xmljs.xml2json(fieldXml)); 129 | const fieldAttr = fXmlJson.elements[0].attributes; 130 | const fieldName = fieldAttr.Name; 131 | const fieldDisplayName = fieldAttr.DisplayName; 132 | 133 | super.log_info('processField', `Processing field ${fieldName} (${fieldDisplayName}) for list ${lc.Title}.`); 134 | fXmlJson.elements[0].attributes.DisplayName = fieldName; 135 | fieldXml = xmljs.json2xml(fXmlJson); 136 | 137 | // Looks like e.g. lookup fields can't be updated, so we'll need to re-create the field 138 | try { 139 | let field = await list.fields.getById(fieldAttr.ID); 140 | await field.delete(); 141 | super.log_info('processField', `Field ${fieldName} (${fieldDisplayName}) successfully deleted from list ${lc.Title}.`); 142 | } catch (err) { 143 | super.log_info('processField', `Field ${fieldName} (${fieldDisplayName}) does not exist in list ${lc.Title}.`); 144 | } 145 | 146 | // Looks like e.g. lookup fields can't be updated, so we'll need to re-create the field 147 | try { 148 | let fieldAddResult = await list.fields.createFieldAsXml(this.tokenHelper.replaceTokens(fieldXml)); 149 | await fieldAddResult.field.update({ Title: fieldDisplayName }); 150 | super.log_info('processField', `Field '${fieldDisplayName}' added successfully to list ${lc.Title}.`); 151 | } catch (err) { 152 | super.log_info('processField', `Failed to add field '${fieldDisplayName}' to list ${lc.Title}.`); 153 | } 154 | } 155 | 156 | /** 157 | * Processes field refs for a list 158 | * 159 | * @param {Web} web The web 160 | * @param {IList} list The pnp list 161 | */ 162 | private async processListFieldRefs(web: Web, list: IList): Promise { 163 | if (list.FieldRefs) { 164 | await list.FieldRefs.reduce((chain: any, fieldRef) => chain.then(() => this.processFieldRef(web, list, fieldRef)), Promise.resolve()); 165 | } 166 | } 167 | 168 | /** 169 | * 170 | * Processes a field ref for a list 171 | * 172 | * @param {Web} web The web 173 | * @param {IList} lc The list configuration 174 | * @param {IListInstanceFieldRef} fieldRef The list field ref 175 | */ 176 | private async processFieldRef(web: Web, lc: IList, fieldRef: IListInstanceFieldRef): Promise { 177 | const list = web.lists.getByTitle(lc.Title); 178 | try { 179 | await list.fields.getById(fieldRef.ID).update({ Hidden: fieldRef.Hidden, Required: fieldRef.Required, Title: fieldRef.DisplayName }); 180 | super.log_info('processFieldRef', `Field '${fieldRef.ID}' updated for list ${lc.Title}.`); 181 | } catch (err) { 182 | super.log_info('processFieldRef', `Failed to update field '${fieldRef.ID}' for list ${lc.Title}.`); 183 | } 184 | } 185 | 186 | /** 187 | * Processes views for a list 188 | * 189 | * @param web The web 190 | * @param lc The list configuration 191 | */ 192 | private async processListViews(web: Web, lc: IList): Promise { 193 | if (lc.Views) { 194 | await lc.Views.reduce((chain: any, view) => chain.then(() => this.processView(web, lc, view)), Promise.resolve()); 195 | } 196 | } 197 | 198 | /** 199 | * Processes a view for a list 200 | * 201 | * @param {Web} web The web 202 | * @param {IList} lc The list configuration 203 | * @param {IListView} lvc The view configuration 204 | */ 205 | private async processView(web: Web, lc: IList, lvc: IListView): Promise { 206 | super.log_info('processView', `Processing view ${lvc.Title} for list ${lc.Title}.`); 207 | let view = web.lists.getByTitle(lc.Title).views.getByTitle(lvc.Title); 208 | try { 209 | await view.get(); 210 | await view.update(lvc.AdditionalSettings); 211 | await this.processViewFields(view, lvc); 212 | } catch (err) { 213 | const result = await web.lists.getByTitle(lc.Title).views.add(lvc.Title, lvc.PersonalView, lvc.AdditionalSettings); 214 | super.log_info('processView', `View ${lvc.Title} added successfully to list ${lc.Title}.`); 215 | await this.processViewFields(result.view, lvc); 216 | } 217 | } 218 | 219 | /** 220 | * Processes view fields for a view 221 | * 222 | * @param {any} view The pnp view 223 | * @param {IListView} lvc The view configuration 224 | */ 225 | private async processViewFields(view, lvc: IListView): Promise { 226 | try { 227 | super.log_info('processViewFields', `Processing view fields for view ${lvc.Title}.`); 228 | await view.fields.removeAll(); 229 | await lvc.ViewFields.reduce((chain, viewField) => chain.then(() => view.fields.add(viewField)), Promise.resolve()); 230 | super.log_info('processViewFields', `View fields successfully processed for view ${lvc.Title}.`); 231 | } catch (err) { 232 | super.log_info('processViewFields', `Failed to process view fields for view ${lvc.Title}.`); 233 | } 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/handlers/navigation.ts: -------------------------------------------------------------------------------- 1 | import { HandlerBase } from "./handlerbase"; 2 | import { INavigation, INavigationNode } from "../schema"; 3 | import { Web, NavigationNodes } from "@pnp/sp"; 4 | import { isArray } from "@pnp/common"; 5 | import { replaceUrlTokens } from "../util"; 6 | import { IProvisioningConfig } from "../provisioningconfig"; 7 | 8 | /** 9 | * Describes the Navigation Object Handler 10 | */ 11 | export class Navigation extends HandlerBase { 12 | /** 13 | * Creates a new instance of the Navigation class 14 | * 15 | * @param {IProvisioningConfig} config Provisioning config 16 | */ 17 | constructor(config: IProvisioningConfig) { 18 | super("Navigation", config); 19 | } 20 | 21 | /** 22 | * Provisioning navigation 23 | * 24 | * @param {Navigation} navigation The navigation to provision 25 | */ 26 | public async ProvisionObjects(web: Web, navigation: INavigation): Promise { 27 | super.scope_started(); 28 | let promises = []; 29 | if (isArray(navigation.QuickLaunch)) { 30 | promises.push(this.processNavTree(web.navigation.quicklaunch, navigation.QuickLaunch)); 31 | } 32 | if (isArray(navigation.TopNavigationBar)) { 33 | promises.push(this.processNavTree(web.navigation.topNavigationBar, navigation.TopNavigationBar)); 34 | } 35 | try { 36 | await Promise.all(promises); 37 | super.scope_ended(); 38 | } catch (err) { 39 | super.scope_ended(); 40 | throw err; 41 | } 42 | } 43 | 44 | private async processNavTree(target: NavigationNodes, nodes: INavigationNode[]): Promise { 45 | try { 46 | const existingNodes = await target.get(); 47 | await this.deleteExistingNodes(target); 48 | await nodes.reduce((chain: any, node) => chain.then(() => this.processNode(target, node, existingNodes)), Promise.resolve()); 49 | } catch (err) { 50 | throw err; 51 | } 52 | } 53 | 54 | private async processNode(target: NavigationNodes, node: INavigationNode, existingNodes: any[]): Promise { 55 | let existingNode = existingNodes.filter(n => n.Title === node.Title); 56 | if (existingNode.length === 1 && node.IgnoreExisting !== true) { 57 | node.Url = existingNode[0].Url; 58 | } 59 | try { 60 | const result = await target.add(node.Title, replaceUrlTokens(node.Url, this.config)); 61 | if (isArray(node.Children)) { 62 | await this.processNavTree(result.node.children, node.Children); 63 | } 64 | } catch (err) { 65 | throw err; 66 | } 67 | } 68 | 69 | private async deleteExistingNodes(target: NavigationNodes) { 70 | try { 71 | const existingNodes = await target.get(); 72 | await existingNodes.reduce((chain: Promise, n: any) => chain.then(() => this.deleteNode(target, n.Id)), Promise.resolve()); 73 | } catch (err) { 74 | throw err; 75 | } 76 | } 77 | 78 | private async deleteNode(target: NavigationNodes, id: number): Promise { 79 | try { 80 | await target.getById(id).delete(); 81 | } catch (err) { 82 | throw err; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/handlers/propertybagentries.ts: -------------------------------------------------------------------------------- 1 | import { HandlerBase } from "./handlerbase"; 2 | import { IPropertyBagEntry } from "../schema"; 3 | import * as Util from "../util"; 4 | import { Logger, LogLevel } from "@pnp/logging"; 5 | import { Web } from "@pnp/sp"; 6 | import { IProvisioningConfig} from "../provisioningconfig"; 7 | 8 | /** 9 | * Describes the PropertyBagEntries Object Handler 10 | */ 11 | export class PropertyBagEntries extends HandlerBase { 12 | /** 13 | * Creates a new instance of the PropertyBagEntries class 14 | * 15 | * @param {IProvisioningConfig} config Provisioning config 16 | */ 17 | constructor(config: IProvisioningConfig) { 18 | super("PropertyBagEntries", config); 19 | } 20 | 21 | /** 22 | * Provisioning property bag entries 23 | * 24 | * @param {Web} web The web 25 | * @param {Array} entries The property bag entries to provision 26 | */ 27 | public ProvisionObjects(web: Web, entries: IPropertyBagEntry[]): Promise { 28 | super.scope_started(); 29 | return new Promise((resolve, reject) => { 30 | if (Util.isNode()) { 31 | Logger.write("PropertyBagEntries Handler not supported in Node.", LogLevel.Error); 32 | reject(); 33 | } else if (this.config.spfxContext) { 34 | Logger.write("PropertyBagEntries Handler not supported in SPFx.", LogLevel.Error); 35 | reject(); 36 | } else { 37 | web.get().then(({ ServerRelativeUrl }) => { 38 | let ctx = new SP.ClientContext(ServerRelativeUrl), 39 | spWeb = ctx.get_web(), 40 | propBag = spWeb.get_allProperties(), 41 | idxProps = []; 42 | entries.filter(entry => entry.Overwrite).forEach(entry => { 43 | propBag.set_item(entry.Key, entry.Value); 44 | if (entry.Indexed) { 45 | idxProps.push(Util.base64EncodeString(entry.Key)); 46 | } 47 | }); 48 | spWeb.update(); 49 | ctx.load(propBag); 50 | ctx.executeQueryAsync(() => { 51 | if (idxProps.length > 0) { 52 | propBag.set_item("vti_indexedpropertykeys", idxProps.join("|")); 53 | spWeb.update(); 54 | ctx.executeQueryAsync(() => { 55 | super.scope_ended(); 56 | resolve(); 57 | }, () => { 58 | super.scope_ended(); 59 | reject(); 60 | }); 61 | } else { 62 | super.scope_ended(); 63 | resolve(); 64 | } 65 | }, () => { 66 | super.scope_ended(); 67 | reject(); 68 | }); 69 | }); 70 | } 71 | }); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/handlers/sitefields.ts: -------------------------------------------------------------------------------- 1 | import * as xmljs from 'xml-js'; 2 | import { HandlerBase } from "./handlerbase"; 3 | import { Web, FieldAddResult } from "@pnp/sp"; 4 | import { ProvisioningContext } from "../provisioningcontext"; 5 | import { IProvisioningConfig } from "../provisioningconfig"; 6 | import { TokenHelper } from '../util/tokenhelper'; 7 | 8 | /** 9 | * Describes the Site Fields Object Handler 10 | */ 11 | export class SiteFields extends HandlerBase { 12 | private context: ProvisioningContext; 13 | private tokenHelper: TokenHelper; 14 | 15 | /** 16 | * Creates a new instance of the ObjectSiteFields class 17 | */ 18 | constructor(config: IProvisioningConfig) { 19 | super("SiteFields", config); 20 | } 21 | 22 | /** 23 | * Provisioning Client Side Pages 24 | * 25 | * @param {Web} web The web 26 | * @param {string[]} siteFields The site fields 27 | * @param {ProvisioningContext} context Provisioning context 28 | */ 29 | public async ProvisionObjects(web: Web, siteFields: string[], context: ProvisioningContext): Promise { 30 | this.context = context; 31 | this.tokenHelper = new TokenHelper(this.context, this.config); 32 | super.scope_started(); 33 | try { 34 | this.context.siteFields = (await web.fields.select('Id', 'InternalName').get>()).reduce((obj, l) => { 35 | obj[l.InternalName] = l.Id; 36 | return obj; 37 | }, {}); 38 | await siteFields.reduce((chain: any, schemaXml) => chain.then(() => this.processSiteField(web, schemaXml)), Promise.resolve()); 39 | } catch (err) { 40 | super.scope_ended(); 41 | throw err; 42 | } 43 | } 44 | 45 | /** 46 | * Provision a site field 47 | * 48 | * @param {Web} web The web 49 | * @param {IClientSidePage} clientSidePage Cient side page 50 | */ 51 | private async processSiteField(web: Web, schemaXml: string): Promise { 52 | try { 53 | schemaXml = this.tokenHelper.replaceTokens(schemaXml); 54 | const schemaXmlJson = JSON.parse(xmljs.xml2json(schemaXml)); 55 | const { DisplayName, Name } = schemaXmlJson.elements[0].attributes; 56 | if (this.context.siteFields[Name]) { 57 | super.log_info("processSiteField", `Updating site field ${DisplayName}`); 58 | return await web.fields.getByInternalNameOrTitle(Name).update({ SchemaXml: schemaXml }); 59 | } else { 60 | super.log_info("processSiteField", `Adding site field ${DisplayName}`); 61 | return await web.fields.createFieldAsXml(schemaXml); 62 | } 63 | } catch (err) { 64 | throw err; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/handlers/websettings.ts: -------------------------------------------------------------------------------- 1 | import { HandlerBase } from "./handlerbase"; 2 | import { IWebSettings } from "../schema"; 3 | import { Web } from "@pnp/sp"; 4 | import * as omit from "object.omit"; 5 | import { replaceUrlTokens } from "../util"; 6 | import { IProvisioningConfig} from "../provisioningconfig"; 7 | 8 | /** 9 | * Describes the WebSettings Object Handler 10 | */ 11 | export class WebSettings extends HandlerBase { 12 | /** 13 | * Creates a new instance of the WebSettings class 14 | * 15 | * @param {IProvisioningConfig} config Provisioning config 16 | */ 17 | constructor(config: IProvisioningConfig) { 18 | super("WebSettings", config); 19 | } 20 | 21 | /** 22 | * Provisioning WebSettings 23 | * 24 | * @param {Web} web The web 25 | * @param {IWebSettings} settings The settings 26 | */ 27 | public async ProvisionObjects(web: Web, settings: IWebSettings): Promise { 28 | super.scope_started(); 29 | Object.keys(settings) 30 | .filter(key => typeof (settings[key]) === "string") 31 | .forEach(key => { 32 | let value: string = settings[key]; 33 | settings[key] = replaceUrlTokens(value, this.config); 34 | }); 35 | try { 36 | await Promise.all([ 37 | web.rootFolder.update({ WelcomePage: settings.WelcomePage }), 38 | web.update(omit(settings, "WelcomePage")), 39 | ]); 40 | super.scope_ended(); 41 | } catch (err) { 42 | super.scope_ended(); 43 | throw err; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { Schema } from "./schema"; 2 | export { WebProvisioner } from "./webprovisioner"; 3 | export { Web } from "@pnp/sp"; 4 | -------------------------------------------------------------------------------- /src/provisioningconfig.ts: -------------------------------------------------------------------------------- 1 | import { LogLevel } from "@pnp/logging"; 2 | 3 | export type ProvisioningParameters = { [key: string]: string }; 4 | 5 | export interface IProvisioningLogging { 6 | activeLogLevel?: LogLevel; 7 | prefix?: string; 8 | } 9 | 10 | /** 11 | * Describes the Provisioning Config 12 | */ 13 | export interface IProvisioningConfig { 14 | parameters?: ProvisioningParameters; 15 | spfxContext?; 16 | logging?: IProvisioningLogging; 17 | } 18 | -------------------------------------------------------------------------------- /src/provisioningcontext.ts: -------------------------------------------------------------------------------- 1 | import {IContentType} from "./schema"; 2 | 3 | /** 4 | * Describes the Provisioning Context 5 | */ 6 | export class ProvisioningContext { 7 | public web = null; 8 | public lists: { [key: string]: string } = {}; 9 | public siteFields: { [key: string]: string } = {}; 10 | public contentTypes: { [key: string]: IContentType } = {}; 11 | 12 | /** 13 | * Creates a new instance of the ProvisioningContext class 14 | */ 15 | constructor() { } 16 | } 17 | -------------------------------------------------------------------------------- /src/schema.ts: -------------------------------------------------------------------------------- 1 | import { CanvasColumnFactorType } from "@pnp/sp"; 2 | export interface Schema { 3 | Navigation?: INavigation; 4 | CustomActions?: ICustomAction[]; 5 | ComposedLook?: IComposedLook; 6 | WebSettings?: IWebSettings; 7 | Features?: IFeature[]; 8 | Lists?: IList[]; 9 | Files?: IFile[]; 10 | PropertyBagEntries?: IPropertyBagEntry[]; 11 | ClientSidePages?: IClientSidePage[]; 12 | SiteFields?: string[]; 13 | ContentTypes: IContentType[]; 14 | [key: string]: any; 15 | } 16 | 17 | export default Schema; 18 | 19 | export interface IFieldRef { 20 | ID: string; 21 | Name?: string; 22 | Required?: boolean; 23 | Hidden?: boolean; 24 | } 25 | 26 | export interface IContentType { 27 | ID: string; 28 | Name: string; 29 | Description: string; 30 | Group: string; 31 | FieldRefs: IFieldRef[]; 32 | } 33 | 34 | export interface IClientSideControl { 35 | Title: string; 36 | Description?: string; 37 | ClientSideComponentId: string; 38 | ClientSideComponentProperties?: any; 39 | } 40 | 41 | export interface IClientSidePageColumn { 42 | Factor: CanvasColumnFactorType; 43 | Controls: IClientSideControl[]; 44 | } 45 | 46 | export interface IClientSidePageSection { 47 | Columns: IClientSidePageColumn[]; 48 | } 49 | 50 | export interface IClientSidePage { 51 | Name: string; 52 | Title: string; 53 | LibraryTitle?: string; 54 | PageLayoutType?: string; 55 | CommentsDisabled?: boolean; 56 | Sections?: IClientSidePageSection[]; 57 | } 58 | 59 | export interface IFeature { 60 | id: string; 61 | deactivate: boolean; 62 | force: boolean; 63 | } 64 | 65 | export interface IFile { 66 | Folder: string; 67 | Src: string; 68 | Url: string; 69 | Overwrite?: boolean; 70 | RemoveExistingWebParts?: boolean; 71 | WebParts?: IWebPart[]; 72 | Properties?: { [key: string]: string | number }; 73 | } 74 | 75 | export interface IWebPartPropertyOverride { 76 | name: string; 77 | type: string; 78 | value: string; 79 | } 80 | 81 | export interface IWebPart { 82 | Title: string; 83 | Zone: string; 84 | Order: number; 85 | Contents: IWebPartContents; 86 | PropertyOverrides?: IWebPartPropertyOverride[]; 87 | ListView?: { 88 | List: string; 89 | View: IListView; 90 | }; 91 | } 92 | 93 | export interface IWebPartContents { 94 | Xml?: string; 95 | FileSrc?: string; 96 | } 97 | 98 | export interface IComposedLook { 99 | ColorPaletteUrl: string; 100 | FontSchemeUrl: string; 101 | BackgroundImageUrl: string; 102 | } 103 | 104 | export interface ICustomAction { 105 | Name: string; 106 | Description?: string; 107 | Title: string; 108 | Location: string; 109 | Url: string; 110 | 111 | [key: string]: string; 112 | } 113 | 114 | export interface IWebSettings { 115 | WelcomePage?: string; 116 | AlternateCssUrl?: string; 117 | SaveSiteAsTemplateEnabled?: boolean; 118 | MasterUrl?: string; 119 | CustomMasterUrl?: string; 120 | RecycleBinEnabled?: boolean; 121 | TreeViewEnabled?: boolean; 122 | QuickLaunchEnabled?: boolean; 123 | SiteLogoUrl?: string; 124 | 125 | [key: string]: string | boolean; 126 | } 127 | 128 | export interface INavigation { 129 | QuickLaunch?: INavigationNode[]; 130 | TopNavigationBar?: INavigationNode[]; 131 | } 132 | 133 | export interface INavigationNode { 134 | Title: string; 135 | Url: string; 136 | IgnoreExisting?: boolean; 137 | Children?: INavigationNode[]; 138 | } 139 | 140 | export interface IRoleAssignment { 141 | Principal: string; 142 | RoleDefinition: string; 143 | } 144 | 145 | export interface IListSecurity { 146 | BreakRoleInheritance?: boolean; 147 | CopyRoleAssignments?: boolean; 148 | ClearSubscopes?: boolean; 149 | RoleAssignments?: IRoleAssignment[]; 150 | } 151 | 152 | export interface IList { 153 | Title: string; 154 | Description: string; 155 | Template: number; 156 | ContentTypesEnabled: boolean; 157 | RemoveExistingContentTypes?: boolean; 158 | ContentTypeBindings?: IContentTypeBinding[]; 159 | Fields?: string[]; 160 | FieldRefs?: IListInstanceFieldRef[]; 161 | Views?: IListView[]; 162 | Security?: IListSecurity; 163 | 164 | AdditionalSettings?: { 165 | DefaultContentApprovalWorkflowId?: string; 166 | DefaultDisplayFormUrl?: string; 167 | DefaultEditFormUrl?: string; 168 | DefaultNewFormUrl?: string; 169 | Description?: string; 170 | Direction?: string; 171 | DocumentTemplateUrl?: string; 172 | /** 173 | * Reader = 0; Author = 1; Approver = 2. 174 | */ 175 | DraftVersionVisibility?: number; 176 | EnableAttachments?: boolean; 177 | EnableFolderCreation?: boolean; 178 | EnableMinorVersions?: boolean; 179 | EnableModeration?: boolean; 180 | EnableVersioning?: boolean; 181 | ForceCheckout?: boolean; 182 | Hidden?: boolean; 183 | IrmEnabled?: boolean; 184 | IrmExpire?: boolean; 185 | IrmReject?: boolean; 186 | IsApplicationList?: boolean; 187 | NoCrawl?: boolean; 188 | OnQuickLaunch?: boolean; 189 | Title?: string; 190 | ValidationFormula?: string; 191 | ValidationMessage?: string; 192 | 193 | [key: string]: string | boolean | number; 194 | }; 195 | } 196 | 197 | 198 | 199 | export interface IListInstanceFieldRef extends IFieldRef { 200 | DisplayName?: string; 201 | } 202 | 203 | export interface IContentTypeBinding { 204 | ContentTypeID: string; 205 | Name?: string; 206 | } 207 | 208 | export interface IListView { 209 | Title: string; 210 | PersonalView?: boolean; 211 | ViewFields?: string[]; 212 | AdditionalSettings?: { 213 | ViewQuery?: string; 214 | RowLimit?: number; 215 | Paged?: boolean; 216 | Hidden?: boolean; 217 | Scope?: 0 | 1; 218 | DefaultView?: boolean; 219 | JSLink?: string; 220 | }; 221 | } 222 | 223 | export interface IPropertyBagEntry { 224 | Key: string; 225 | Value: string; 226 | Indexed?: boolean; 227 | Overwrite?: boolean; 228 | } 229 | -------------------------------------------------------------------------------- /src/util/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import { IProvisioningConfig } from "../provisioningconfig"; 3 | 4 | export function replaceUrlTokens(str: string, config: IProvisioningConfig): string { 5 | let siteAbsoluteUrl = null; 6 | if (config.spfxContext) { 7 | siteAbsoluteUrl = config.spfxContext.pageContext.site.absoluteUrl; 8 | } else if (window.hasOwnProperty("_spPageContextInfo")) { 9 | siteAbsoluteUrl = _spPageContextInfo.siteAbsoluteUrl; 10 | } 11 | return str 12 | .replace(/{sitecollection}/g, siteAbsoluteUrl) 13 | .replace(/{wpgallery}/g, `${siteAbsoluteUrl}/_catalogs/wp`) 14 | .replace(/{hosturl}/g, `${window.location.protocol}//${window.location.host}:${window.location.port}`) 15 | .replace(/{themegallery}/g, `${siteAbsoluteUrl}/_catalogs/theme/15`); 16 | } 17 | 18 | export function makeUrlRelative(absUrl: string): string { 19 | return absUrl.replace(`${document.location.protocol}//${document.location.hostname}`, ""); 20 | } 21 | 22 | export function base64EncodeString(str: string): string { 23 | let bytes = []; 24 | for (let i = 0; i < str.length; ++i) { 25 | bytes.push(str.charCodeAt(i)); 26 | bytes.push(0); 27 | } 28 | let b64encoded = window.btoa(String.fromCharCode.apply(null, bytes)); 29 | return b64encoded; 30 | } 31 | 32 | export function isNode(): boolean { 33 | return typeof window === "undefined"; 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/util/tokenhelper.ts: -------------------------------------------------------------------------------- 1 | 2 | import { ProvisioningContext } from "../provisioningcontext"; 3 | import { IProvisioningConfig } from "../provisioningconfig"; 4 | 5 | /** 6 | * Describes the Token Helper 7 | */ 8 | export class TokenHelper { 9 | private tokenRegex = /{[a-z]*:[ÆØÅæøåA-za-z ]*}/g; 10 | 11 | /** 12 | * Creates a new instance of the TokenHelper class 13 | */ 14 | constructor( 15 | public context: ProvisioningContext, 16 | public config: IProvisioningConfig 17 | ) { } 18 | 19 | public replaceTokens(str: string) { 20 | let m; 21 | while ((m = this.tokenRegex.exec(str)) !== null) { 22 | if (m.index === this.tokenRegex.lastIndex) { 23 | this.tokenRegex.lastIndex++; 24 | } 25 | m.forEach((match) => { 26 | let [tokenKey, tokenValue] = match.replace(/[\{\}]/g, "").split(":"); 27 | switch (tokenKey) { 28 | case "listid": { 29 | if (this.context.lists[tokenValue]) { 30 | str = str.replace(match, this.context.lists[tokenValue]); 31 | } 32 | } 33 | break; 34 | case "siteid": { 35 | if (this.context.web.Id) { 36 | str = str.replace(match, this.context.web.Id); 37 | } 38 | } 39 | break; 40 | case "parameter": { 41 | if (this.config.parameters) { 42 | const paramValue = this.config.parameters[tokenValue]; 43 | if (paramValue) { 44 | str = str.replace(match, paramValue); 45 | } 46 | } 47 | } 48 | break; 49 | } 50 | }); 51 | } 52 | return str; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/webprovisioner.ts: -------------------------------------------------------------------------------- 1 | // we need to import HandlerBase & TypedHash to avoid naming issues in ts transpile 2 | import { Schema } from "./schema"; 3 | import { HandlerBase } from "./handlers/handlerbase"; 4 | import { Web } from "@pnp/sp"; 5 | import { TypedHash } from "@pnp/common"; 6 | import { Logger, LogLevel, ConsoleListener } from "@pnp/logging"; 7 | import { DefaultHandlerMap, DefaultHandlerSort } from "./handlers/exports"; 8 | import { ProvisioningContext } from "./provisioningcontext"; 9 | import { IProvisioningConfig } from "./provisioningconfig"; 10 | 11 | /** 12 | * Root class of Provisioning 13 | */ 14 | export class WebProvisioner { 15 | public handlerMap: TypedHash; 16 | private context: ProvisioningContext = new ProvisioningContext(); 17 | private config: IProvisioningConfig; 18 | /** 19 | * Creates a new instance of the Provisioner class 20 | * 21 | * @param {Web} web The Web instance to which we want to apply templates 22 | * @param {TypedHash} handlermap A set of handlers we want to apply. The keys of the map need to match the property names in the template 23 | */ 24 | constructor(private web: Web, public handlerSort: TypedHash = DefaultHandlerSort) { } 25 | 26 | private async onSetup() { 27 | if (this.config && this.config.logging) { 28 | Logger.subscribe(new ConsoleListener()); 29 | Logger.activeLogLevel = this.config.logging.activeLogLevel; 30 | } 31 | this.handlerMap = DefaultHandlerMap(this.config); 32 | this.context.web = await this.web.get(); 33 | } 34 | 35 | /** 36 | * Applies the supplied template to the web used to create this Provisioner instance 37 | * 38 | * @param {Schema} template The template to apply 39 | * @param {string[]} handlers A set of handlers we want to apply 40 | * @param {Function} progressCallback Callback for progress updates 41 | */ 42 | public async applyTemplate(template: Schema, handlers?: string[], progressCallback?: (msg: string) => void): Promise { 43 | await this.onSetup(); 44 | 45 | let operations = Object.getOwnPropertyNames(template) 46 | .sort((name1: string, name2: string) => { 47 | let sort1 = this.handlerSort.hasOwnProperty(name1) ? this.handlerSort[name1] : 99; 48 | let sort2 = this.handlerSort.hasOwnProperty(name2) ? this.handlerSort[name2] : 99; 49 | return sort1 - sort2; 50 | }); 51 | 52 | if (handlers) { 53 | operations = operations.filter(op => handlers.indexOf(op) !== -1); 54 | } 55 | 56 | try { 57 | await operations.reduce((chain: any, name: string) => { 58 | let handler = this.handlerMap[name]; 59 | return chain.then(() => { 60 | if (progressCallback) { 61 | progressCallback(name); 62 | } 63 | return handler.ProvisionObjects(this.web, template[name], this.context); 64 | }); 65 | }, Promise.resolve()); 66 | } catch (error) { 67 | throw error; 68 | } 69 | } 70 | 71 | /** 72 | * Sets up the web provisioner 73 | * 74 | * @param {IProvisioningConfig} config Provisioning config 75 | */ 76 | public setup(config: IProvisioningConfig) { 77 | this.config = config; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tests/test-config.test.ts: -------------------------------------------------------------------------------- 1 | declare var global: any; 2 | import * as chai from "chai"; 3 | import "mocha"; 4 | import { sp, Web } from "@pnp/sp"; 5 | import { extend, combine, getGUID } from "@pnp/common"; 6 | import { SPFetchClient } from "@pnp/nodejs"; 7 | import * as chaiAsPromised from "chai-as-promised"; 8 | chai.use(chaiAsPromised); 9 | 10 | export let testSettings = extend(global.settings.testing, { webUrl: "" }); 11 | 12 | before(function (done: MochaDone) { 13 | 14 | // this may take some time, don't timeout early 15 | this.timeout(90000); 16 | 17 | // establish the connection to sharepoint 18 | if (testSettings.enableWebTests) { 19 | 20 | sp.setup({ 21 | sp: { 22 | fetchClientFactory: () => { 23 | return new SPFetchClient(testSettings.siteUrl, testSettings.clientId, testSettings.clientSecret); 24 | }, 25 | }, 26 | }); 27 | 28 | // comment this out to keep older subsites 29 | // cleanUpAllSubsites(); 30 | 31 | // create the web in which we will test 32 | let d = new Date(); 33 | let g = getGUID(); 34 | 35 | sp.web.webs.add(`PnP-JS-Provisioning Testing ${d.toDateString()}`, g).then(() => { 36 | 37 | let url = combine(testSettings.siteUrl, g); 38 | 39 | // set the testing web url so our tests have access if needed 40 | testSettings.webUrl = url; 41 | 42 | // re-setup the node client to use the new web 43 | sp.setup({ 44 | // headers: { 45 | // "Accept": "application/json;odata=verbose", 46 | // }, 47 | sp: { 48 | fetchClientFactory: () => { 49 | return new SPFetchClient(url, testSettings.clientId, testSettings.clientSecret); 50 | }, 51 | }, 52 | }); 53 | 54 | done(); 55 | }); 56 | } else { 57 | done(); 58 | } 59 | }); 60 | 61 | after(() => { 62 | 63 | // could remove the sub web here? 64 | // clean up other stuff? 65 | // write some logging? 66 | }); 67 | 68 | // this can be used to clean up lots of test sub webs :) 69 | export function cleanUpAllSubsites() { 70 | sp.site.rootWeb.webs.select("Title").get().then((w) => { 71 | w.forEach((element: any) => { 72 | let web = new Web(element["odata.id"], ""); 73 | web.webs.select("Title").get().then((sw: any[]) => { 74 | return Promise.all(sw.map((value) => { 75 | let web2 = new Web(value["odata.id"], ""); 76 | return web2.delete(); 77 | })); 78 | }).then(() => { web.delete(); }); 79 | }); 80 | }).catch(e => console.log("Error: " + JSON.stringify(e))); 81 | } 82 | -------------------------------------------------------------------------------- /tests/testutils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to create an escaped regex for the non-SharePoint request tests 3 | * 4 | */ 5 | export function toMatchEndRegex(s: string): RegExp { 6 | let s2 = s.replace(/\(/g, "\\("); 7 | s2 = s2.replace(/\)/g, "\\)"); 8 | s2 = s2.replace(/\?/g, "\\?"); 9 | s2 = s2.replace(/\$/g, "\\$"); 10 | return new RegExp(s2 + "$"); 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "forceConsistentCasingInFileNames": true, 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "jsx": "react", 8 | "declaration": true, 9 | "sourceMap": true, 10 | "experimentalDecorators": true, 11 | "skipLibCheck": true, 12 | "outDir": "lib", 13 | "typeRoots": [ 14 | "./node_modules/@types", 15 | "./node_modules/@microsoft" 16 | ], 17 | "types": [ 18 | "es6-promise", 19 | "webpack-env", 20 | "sharepoint" 21 | ], 22 | "lib": [ 23 | "es5", 24 | "dom", 25 | "es2015.collection" 26 | ] 27 | }, 28 | "include": [ 29 | "src/**/*.ts" 30 | ], 31 | "exclude": [ 32 | "node_modules", 33 | "lib" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@microsoft/sp-tslint-rules/base-tslint.json", 3 | "rules": { 4 | "class-name": false, 5 | "export-name": false, 6 | "forin": false, 7 | "label-position": false, 8 | "member-access": true, 9 | "no-arg": false, 10 | "no-console": false, 11 | "no-construct": false, 12 | "no-duplicate-variable": true, 13 | "no-eval": false, 14 | "no-function-expression": true, 15 | "no-internal-module": true, 16 | "no-shadowed-variable": true, 17 | "no-switch-case-fall-through": true, 18 | "no-unnecessary-semicolons": true, 19 | "no-unused-expression": true, 20 | "no-with-statement": true, 21 | "semicolon": true, 22 | "trailing-comma": false, 23 | "typedef": false, 24 | "typedef-whitespace": false, 25 | "use-named-parameter": true, 26 | "variable-name": false, 27 | "whitespace": false 28 | } 29 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require("path"), 2 | webpack = require('webpack'), 3 | config = require('./gulptasks/@configuration.js'); 4 | 5 | module.exports = [{ 6 | // core file + sourcemap -> dist 7 | cache: true, 8 | entry: './lib/webprovisioner.js', 9 | output: { 10 | path: path.join(__dirname, "dist"), 11 | publicPath: "/assets/", 12 | filename: "pnp-provisioning.js", 13 | libraryTarget: "umd", 14 | library: "$pnp" 15 | }, 16 | devtool: "source-map", 17 | resolve: { 18 | extensions: ['.js'] 19 | }, 20 | plugins: [ 21 | new webpack.BannerPlugin(config.header) 22 | ], 23 | module: { 24 | loaders: [ 25 | { test: /\.js$/, loader: 'babel-loader?presets[]=es2015' } 26 | ] 27 | } 28 | }, 29 | { 30 | // minified core file + sourcemap -> dist 31 | cache: true, 32 | entry: './lib/webprovisioner.js', 33 | output: { 34 | path: path.join(__dirname, "dist"), 35 | publicPath: "/assets/", 36 | filename: "pnp-provisioning.min.js", 37 | libraryTarget: "umd", 38 | library: "$pnp" 39 | }, 40 | devtool: "source-map", 41 | resolve: { 42 | extensions: ['.js'] 43 | }, 44 | plugins: [ 45 | new webpack.BannerPlugin(config.header), 46 | new webpack.DefinePlugin({ 47 | "process.env": { 48 | "NODE_ENV": JSON.stringify("production") 49 | } 50 | }), 51 | new webpack.optimize.UglifyJsPlugin() 52 | ], 53 | module: { 54 | loaders: [ 55 | { test: /\.js$/, loader: 'babel-loader?presets[]=es2015' }, 56 | ] 57 | } 58 | }]; 59 | --------------------------------------------------------------------------------