├── .gitattributes ├── .npmignore ├── bin └── contentful-clean-space.js ├── tslint.json ├── package.json ├── LICENSE ├── CHANGELOG.md ├── .gitignore ├── README.md ├── tsconfig.json └── src └── main.ts /.gitattributes: -------------------------------------------------------------------------------- 1 | package.json text eol=lf 2 | package-lock.json text eol=lf 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | build/**/*.js.map 3 | .vscode/ 4 | tsconfig.json 5 | tslint.json 6 | -------------------------------------------------------------------------------- /bin/contentful-clean-space.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const index = require("../build/main"); 3 | 4 | (async () => { 5 | try { 6 | await index.main(); 7 | } 8 | catch (e) { 9 | console.log(e); 10 | process.exitCode = 1; 11 | } 12 | })(); 13 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": {}, 7 | "rules": { 8 | "trailing-comma": false, 9 | "no-console": false, 10 | "curly": false, 11 | "no-string-literal": false 12 | }, 13 | "rulesDirectory": [] 14 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "contentful-clean-space", 3 | "version": "0.9.0", 4 | "description": "Removes content from a contentful space.", 5 | "engines": { 6 | "node": ">=12" 7 | }, 8 | "bin": { 9 | "contentful-clean-space": "./bin/contentful-clean-space.js" 10 | }, 11 | "scripts": { 12 | "build": "node ./node_modules/typescript/bin/tsc --outDir ./build", 13 | "test": "echo \"Error: no test specified\" && exit 1", 14 | "prettier:write": "prettier --write ./src" 15 | }, 16 | "author": "Stephan Oehlert ([ 100 | { 101 | type: "confirm", 102 | name: "yes", 103 | message: `Do you really want to delete all entries from space ${spaceId}:${environment}?`, 104 | }, 105 | ]); 106 | return prompt.yes; 107 | } 108 | 109 | async function promptForContentTypesConfirmation( 110 | spaceId: string, 111 | environment: string 112 | ) { 113 | const prompt = await inquirer.prompt<{ yes: boolean }>([ 114 | { 115 | type: "confirm", 116 | name: "yes", 117 | message: `Do you really want to delete all content types from space ${spaceId}:${environment}?`, 118 | }, 119 | ]); 120 | return prompt.yes; 121 | } 122 | 123 | async function promptForAssetsConfirmation( 124 | spaceId: string, 125 | environment: string 126 | ) { 127 | const prompt = await inquirer.prompt<{ yes: boolean }>([ 128 | { 129 | type: "confirm", 130 | name: "yes", 131 | message: `Do you really want to delete all assets/media from space ${spaceId}:${environment}?`, 132 | }, 133 | ]); 134 | return prompt.yes; 135 | } 136 | 137 | async function deleteEntries( 138 | contentfulSpace: Space, 139 | batchSize: number, 140 | verbose: boolean, 141 | environment: string 142 | ) { 143 | const selectedEnvironment = await contentfulSpace.getEnvironment(environment); 144 | const entriesMetadata = await selectedEnvironment.getEntries({ 145 | include: 0, 146 | limit: 0, 147 | }); 148 | let totalEntries = entriesMetadata.total; 149 | console.log(`Deleting ${totalEntries} entries`); 150 | 151 | // tslint:disable-next-line:max-line-length 152 | const entriesProgressBar = new ProgressBar( 153 | "Deleting entries [:bar], rate: :rate/s, done: :percent, time left: :etas", 154 | { total: totalEntries } 155 | ); 156 | do { 157 | const entries = await selectedEnvironment.getEntries({ 158 | include: 0, 159 | limit: batchSize, 160 | }); 161 | totalEntries = entries.total; 162 | 163 | const promises: Array> = []; 164 | for (const entry of entries.items) { 165 | const promise = unpublishAndDeleteEntry( 166 | entry, 167 | entriesProgressBar, 168 | verbose 169 | ); 170 | promises.push(promise); 171 | } 172 | await Promise.all(promises); 173 | } while (totalEntries > batchSize); 174 | } 175 | 176 | async function unpublishAndDeleteEntry( 177 | entry: Entry | Asset, 178 | progressBar: ProgressBar, 179 | verbose: boolean 180 | ) { 181 | try { 182 | if (entry.isPublished()) { 183 | if (verbose) console.log(`Unpublishing entry "${entry.sys.id}"`); 184 | await entry.unpublish(); 185 | } 186 | if (verbose) console.log(`Deleting entry '${entry.sys.id}"`); 187 | await entry.delete(); 188 | } catch (e) { 189 | console.log(e); 190 | // Continue if something went wrong with Contentful 191 | } finally { 192 | progressBar.tick(); 193 | } 194 | } 195 | 196 | async function deleteContentTypes( 197 | contentfulSpace: Space, 198 | batchSize: number, 199 | verbose: boolean, 200 | environment: string 201 | ) { 202 | const selectedEnvironment = await contentfulSpace.getEnvironment(environment); 203 | const contentTypesMetadata = await selectedEnvironment.getContentTypes({ 204 | include: 0, 205 | limit: 0, 206 | }); 207 | let totalContentTypes = contentTypesMetadata.total; 208 | console.log(`Deleting ${totalContentTypes} content types`); 209 | 210 | // tslint:disable-next-line:max-line-length 211 | const contentTypesProgressBar = new ProgressBar( 212 | "Deleting content types [:bar], rate: :rate/s, done: :percent, time left: :etas", 213 | { total: totalContentTypes } 214 | ); 215 | do { 216 | const contentTypes = await selectedEnvironment.getContentTypes({ 217 | include: 0, 218 | limit: batchSize, 219 | }); 220 | totalContentTypes = contentTypes.total; 221 | 222 | const promises: Array> = []; 223 | for (const contentType of contentTypes.items) { 224 | const promise = unpublishAndDeleteContentType( 225 | contentType, 226 | contentTypesProgressBar, 227 | verbose 228 | ); 229 | promises.push(promise); 230 | } 231 | await Promise.all(promises); 232 | } while (totalContentTypes > batchSize); 233 | } 234 | 235 | async function unpublishAndDeleteContentType( 236 | contentType: ContentType, 237 | progressBar: ProgressBar, 238 | verbose: boolean 239 | ) { 240 | try { 241 | if (contentType.isPublished()) { 242 | if (verbose) 243 | console.log(`Unpublishing content type "${contentType.sys.id}"`); 244 | await contentType.unpublish(); 245 | } 246 | if (verbose) console.log(`Deleting content type '${contentType.sys.id}"`); 247 | await contentType.delete(); 248 | } catch (e) { 249 | console.log(e); 250 | // Continue if something went wrong with Contentful 251 | } finally { 252 | progressBar.tick(); 253 | } 254 | } 255 | 256 | async function deleteAssets( 257 | contentfulSpace: Space, 258 | batchSize: number, 259 | verbose: boolean, 260 | environment: string 261 | ) { 262 | const selectedEnvironment = await contentfulSpace.getEnvironment(environment); 263 | const assetsMetadata = await selectedEnvironment.getAssets({ 264 | include: 0, 265 | limit: 0, 266 | }); 267 | let totalAssets = assetsMetadata.total; 268 | console.log(`Deleting ${totalAssets} assets/media`); 269 | 270 | // tslint:disable-next-line:max-line-length 271 | const entriesProgressBar = new ProgressBar( 272 | "Deleting assets [:bar], rate: :rate/s, done: :percent, time left: :etas", 273 | { total: totalAssets } 274 | ); 275 | do { 276 | const assets = await selectedEnvironment.getAssets({ 277 | include: 0, 278 | limit: batchSize, 279 | }); 280 | totalAssets = assets.total; 281 | 282 | const promises: Array> = []; 283 | for (const asset of assets.items) { 284 | const promise = unpublishAndDeleteEntry( 285 | asset, 286 | entriesProgressBar, 287 | verbose 288 | ); 289 | promises.push(promise); 290 | } 291 | await Promise.all(promises); 292 | } while (totalAssets > batchSize); 293 | } 294 | --------------------------------------------------------------------------------