Skip to content

[DEVOPS-3113] Add ability to version static assets #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 49 commits into from
Dec 2, 2021
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
cab63cd
Add the asset versioning logic from doc-extension-framework repo
ep-linden Nov 16, 2021
d235d89
Fix typo
ep-linden Nov 16, 2021
c5679e7
Refactor move asset files function to be modular
ep-linden Nov 16, 2021
93311dc
Remove incomplete function
ep-linden Nov 16, 2021
6185a62
Refactor commander to specify paramters in CLI when assigning values …
ep-linden Nov 17, 2021
6400f03
Add assetType parameter to createVersion module
ep-linden Nov 17, 2021
c4b66aa
Fix bug because create function for creatVersion was missing.
ep-linden Nov 17, 2021
ebc7d27
Set store options as properties to store it as the commander object's…
ep-linden Nov 17, 2021
a9930eb
Change parameter name to staticDir and change assetVersioning to stat…
ep-linden Nov 17, 2021
122c00f
Refactor logic for versioning static asset files to be backwards comp…
ep-linden Nov 18, 2021
17a806b
Rename static types to static assets to improve clarity. Change funct…
ep-linden Nov 18, 2021
feb7378
Reverse changes to static versioning function to use newer core funct…
ep-linden Nov 18, 2021
65d2c85
Seperate function into smaller functions for replacing relative paths…
ep-linden Nov 19, 2021
0038548
Place replacing relative function into versioning functionality. Add …
ep-linden Nov 21, 2021
02b4153
Remove old commented out funciton
ep-linden Nov 21, 2021
f2aeef5
Rename function for clarity
ep-linden Nov 21, 2021
5e0ac95
Remove unused console log in versioning function
ep-linden Nov 21, 2021
d570e38
Add comment for the new logic
ep-linden Nov 21, 2021
439caec
Improve the comment for update relative path function
ep-linden Nov 21, 2021
4f8451c
Rename static assets variable and pass that variable to the createVer…
ep-linden Nov 21, 2021
ee6cc1a
Add blankline in snapshot version file. Use node fs.access function t…
ep-linden Nov 22, 2021
f785227
Add unit test for the static asset function. Add rewire to dev depend…
ep-linden Nov 24, 2021
7054fab
Add ./ and remove blank line
ep-linden Nov 24, 2021
d0b0a07
Remove unused function and add blank line at end of file.
ep-linden Nov 24, 2021
7f24e01
Handle the version error in the CLI and refactor accordingly. Change …
ep-linden Nov 24, 2021
5c55029
Handle error when static asset type directory does not exist. Change …
ep-linden Nov 25, 2021
a319847
Use fs.access instead of opendir to check if static asset folder exis…
ep-linden Nov 25, 2021
d453efc
Refactor static asset versioning logic to handle the potential versio…
ep-linden Nov 26, 2021
a36d930
Fix typo in paths
ep-linden Nov 26, 2021
c9fd896
Add a then block to prevent unnecessary file search if the static ass…
ep-linden Nov 26, 2021
1a75a09
Update staticVersionTest to match with new changes in staticVersioner…
ep-linden Nov 29, 2021
a704fad
Remove variable checks in snapshotVersion and move them into createVe…
ep-linden Nov 29, 2021
678bd5b
Remove console.log in createVersion
ep-linden Nov 29, 2021
4663f5e
Add semicolon in createVersion. Add tests for createVersion because c…
ep-linden Nov 29, 2021
851a952
Refacator code based on feedback. Use fs.access instead of fs.existsS…
ep-linden Nov 30, 2021
945a9ba
Add additional describes to explain the scenarios in the staticVersio…
ep-linden Dec 1, 2021
af345bd
Change if check in removeFilesInDirectory function check an array of …
ep-linden Dec 1, 2021
1507079
Add checks for staticDir in the siteUtilsTest.js.
ep-linden Dec 1, 2021
157dcd3
Add s to error message. Add logic to check if the docs repo as a whol…
ep-linden Dec 1, 2021
4f8aa9d
Set replacement text for versioned_docs based on if condition.
ep-linden Dec 1, 2021
2b3a68c
Change search pattern to replace in versioned_docs dir.
ep-linden Dec 1, 2021
c5e9a6a
Move options variables inside the create function since the standalon…
ep-linden Dec 2, 2021
df87a13
Declare the replacementText variable for each updateRelativePath to i…
ep-linden Dec 2, 2021
6c6eb11
Organize test suite. Add more tests to cover the different use-cases …
ep-linden Dec 2, 2021
d1067f8
Fix and refactor tests in createVersionTest.js.
ep-linden Dec 2, 2021
e3e6d48
Rename test file name. Remove unit test for function call ordering as…
ep-linden Dec 2, 2021
b2bc6d8
Change console.info to avoid confusion with static asset versioning.
ep-linden Dec 2, 2021
dd688b7
Deleted CLI test because no extra logic is done outside of Commander'…
ep-linden Dec 2, 2021
ca16279
Reorganize test cases in staticVersionerTest.js. Update comments for …
ep-linden Dec 2, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"eslint-plugin-mocha": "^8.0.0",
"mocha": "^8.1.1",
"mock-fs": "^4.12.0",
"rewire": "5.0.0",
"sinon": "^9.0.3"
},
"dependencies": {
Expand All @@ -25,4 +26,3 @@
},
"license": "GPL-3.0"
}

2 changes: 1 addition & 1 deletion src/lib/assetCopier.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const readDirectory = util.promisify(fs.readdir);
const copyFile = util.promisify(fs.copyFile);
const lstatFile = util.promisify(fs.lstat);

exports.copyAssets = async (docsDir, version) => {
exports.copyDocAssets = async (docsDir, version) => {
console.info("Copying assets for the new version...");
let pathToAssets = path.join(docsDir, "assets");
if (!fs.existsSync(pathToAssets)) {
Expand Down
33 changes: 21 additions & 12 deletions src/lib/createVersion.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,35 @@ const diffManager = require('./diffManager');
const siteUtils = require('./siteUtils');
const assetCopier = require('./assetCopier');
const linker = require('./linker');
const staticVersioner = require('./staticVersioner')
const path = require("path");

exports.create = (version, siteDir) => {
exports.create = (version, siteDir, staticAssets) => {
let siteProps = siteUtils.loadSiteProperties(siteDir);
throwIfInvalidCommand(version, siteProps);
return createVersion(version, siteProps);
throwIfInvalidCommand(version, siteProps, staticAssets);
return createVersion(version, siteProps, staticAssets);
}

function throwIfInvalidCommand(version, siteProps) {
function throwIfInvalidCommand(version, siteProps, staticAssets) {
if (!fs.existsSync(siteProps.paths.versionJS)) {
throw new Error("version.js file is missing");
}
if (typeof version === undefined) {
throw new TypeError("Verion not specified");
}
if (version.includes('/')) {
throw new TypeError("Invalid version format. (/) character is not allowed in version");
}
if (siteProps.pastVersions.includes(version)) {
throw new TypeError("The specified version already exists");
}
if (staticAssets.length > 0) {
staticAssets.forEach(staticType => {
let staticTypeDirPath = path.join(siteProps.path.staticDir, staticType);
fs.access(staticTypeDirPath, err => {
if (err) {
throw new TypeError(`The ${staticType} directory does not exist under the static directory`)
}
})
})
}
}

async function createVersion(version, siteProps) {
async function createVersion(version, siteProps, staticAssets) {
await Promise.all([diffManager.generateFileDiff(siteProps.paths.docs), diffManager.generateSidebarDiff(siteProps.paths.siteDir)]);
console.info("Diff generation completed...");
runDocusaurusVersionCommand(version, siteProps.paths.siteDir);
Expand All @@ -35,8 +41,11 @@ async function createVersion(version, siteProps) {
diffManager.cleanUpFileDiff(siteProps.paths.versionedDocs, version),
diffManager.cleanUpSidebarDiff(siteProps.paths.siteDir),
diffManager.cleanUpSidebarDiff(siteProps.paths.siteDir, version),
assetCopier.copyAssets(siteProps.paths.docs, version)
assetCopier.copyDocAssets(siteProps.paths.docs, version),
]);
if (staticAssets) {
await staticVersioner.versionStaticAssets(siteProps.paths, staticAssets, version);
}
return linker.linkAssetsInMarkdownFiles(siteProps.paths.versionedDocs, version);
}

Expand Down
2 changes: 2 additions & 0 deletions src/lib/siteUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ exports.loadSiteProperties = (siteDir) => {
let versionJSONPath = path.join(resolvedSiteDir, "versions.json");
let siteConfigPath = path.join(resolvedSiteDir, "siteConfig.js");
let versionedDocsPath = path.join(resolvedSiteDir, "versioned_docs");
let staticDirPath = path.join(resolvedSiteDir, "static");
let pastVersions = [];

if(fs.existsSync(versionJSONPath)) {
Expand All @@ -30,6 +31,7 @@ exports.loadSiteProperties = (siteDir) => {
siteProps.paths.siteConfig = siteConfigPath;
siteProps.paths.docs = docsPath;
siteProps.paths.versionedDocs = versionedDocsPath;
siteProps.paths.staticDir = staticDirPath;

siteProps.pastVersions = pastVersions;
siteProps.siteConfig = siteConfig;
Expand Down
113 changes: 113 additions & 0 deletions src/lib/staticVersioner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
const fs = require('fs').promises;
const path = require('path');

async function versionStaticAssets(sitePaths, staticAssets, version) {
let numberOfVersions = await getNumberOfVersions();
let staticDir = sitePaths.staticDir;
let versionDocsDir = sitePaths.versionedDocs;

for (const staticType of staticAssets) {
console.info("Versioning static asset files...");
let staticTypePath = path.join(staticDir, staticType);
let staticTypeNextPath = path.join(staticTypePath, "next");
// if a static asset is versioned for the first time, create the next directory
await fs.access(staticTypeNextPath)
.catch(async () => {
await copyDirectory(staticTypePath, staticTypeNextPath);
await removeFilesInDirectory(staticTypePath, "next");
})
let staticTypeVersionPath = path.join(staticTypePath, version);
await copyDirectory(staticTypeNextPath, staticTypeVersionPath)
}

let baseVersionedDocsPath = path.join(versionDocsDir, `version-${version}`);
if (numberOfVersions === 1) {
await updateRelativePaths(sitePaths.docs, staticAssets);
}
await updateRelativePaths(baseVersionedDocsPath, staticAssets, version);
}

async function getNumberOfVersions() {
let fileContent = await fs.readFile("./versions.json", "utf8");
let jsonContent = JSON.parse(fileContent);
return jsonContent.length;
}

async function copyDirectory(from, to) {
await fs.mkdir(to);

const files = await fs.readdir(from, {withFileTypes: true});
for (const file of files) {
let relativePath = path.join(from, file.name);
let targetPath = path.join(to, file.name);
if (file.isDirectory() && relativePath !== to) {
await copyDirectory(relativePath, targetPath);
} else if(!file.isDirectory()) {
await fs.copyFile(relativePath, targetPath)
.catch((err) => console.log(err + '\n' + `${relativePath} could not be copied`));
}
}
}

async function removeFilesInDirectory(from, exclude) {
const files = await fs.readdir(from, {withFileTypes: true});
for (const file of files) {
if (exclude !== file.name) {
await fs.rm(path.join(from, file.name), {recursive: true});
}
}
}

/*
Recursive function that loops through all files in the current directory and
subdirectories, and replaces the links in each file.
*/
async function updateRelativePaths(basePath, staticAssets, version="") {
// read all files from directory
const files = await fs.readdir(basePath, {withFileTypes: true});
for (const file of files) {
// if file type is a directory, a recursive call is made
if (file.isDirectory()) {
let subDirName = file.name;
await updateRelativePaths(path.join(basePath, subDirName), staticAssets, version);
} else {
let filePath = path.join(basePath, file.name);
await replaceRelativePaths(filePath, staticAssets, version);
}
}
}

async function replaceRelativePaths(filePath, staticAssets, version="") {
let fileContent = await fs.readFile(filePath, {encoding: "utf8", flag: "r+"});
// flag to set when files are modified
let isFileContentModified = false;
let numberOfVersions = await getNumberOfVersions();

// loop through all the passed in static assets and replace the text
for (const staticType of staticAssets) {

let relativeLinkPattern = new RegExp(`../${staticType}/next`, 'gm');
let replacementText = `${staticType}/${version}`;

// if versioning for the first time, replace the links in the docs path and versioned docs path accordingly
if (numberOfVersions === 1 && filePath.includes('/docs/')) {
relativeLinkPattern = new RegExp(`../${staticType}/`, 'gm')
replacementText = `../../${staticType}/next/`;
} else if (numberOfVersions === 1 && filePath.includes('/versioned_docs/')) {
relativeLinkPattern = new RegExp(`../${staticType}/`, 'gm');
replacementText = `../${staticType}/${version}/`;
}

fileContent = fileContent.replace(relativeLinkPattern, function() {
isFileContentModified = true;
return replacementText;
});
}

// if contents have been modified then write to file
if (isFileContentModified) {
await fs.writeFile(filePath, fileContent);
}
}

module.exports.versionStaticAssets = versionStaticAssets;
22 changes: 18 additions & 4 deletions src/snapshotVersion.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
const program = require('commander');
const createVersion = require('./lib/createVersion.js');
const fs = require("fs");

module.exports = (argsParser = process.argv) => {
program
.command('create <version> [siteDir]')
.action((version, siteDir = ".") => {
createVersion.create(version, siteDir);
.command('create')
.storeOptionsAsProperties(true)
.requiredOption('--version <type>', '[required] The value of the new version to create.')
.option('--siteDir <type>', '[optional] The absolute or relative path to the `website` directory of the Docusaurus V1 project.', '.')
.option('--staticDir <type...>', '[optional] The static asset directory to be versioned. Only the name of the folder is required.')
.action((options) => {
let version = options.version;
let siteDir = options.siteDir;
let staticDir = options.staticDir;
if (version.includes('/')) {
throw new TypeError("Invalid version format. (/) character is not allowed in version");
}
if (!staticDir) {
staticDir = [];
}
createVersion.create(version, siteDir, staticDir);
});
program.parse(argsParser);
}
}
2 changes: 1 addition & 1 deletion test/lib/assetCopierTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe("Copy assets for versioning", function() {
});

let docsDir = path.join(process.cwd(), "docs");
await assetCopier.copyAssets(docsDir, "1.2.4");
await assetCopier.copyDocAssets(docsDir, "1.2.4");
let versionedAssetPath = path.join(docsDir, "assets", "version-1.2.4");
let versionedOverviewPath = path.join(versionedAssetPath, "Overview.png");
let versionedAPIPath = path.join(versionedAssetPath, "API");
Expand Down
6 changes: 3 additions & 3 deletions test/lib/createVersionTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ describe('createVersion does preliminary checks and calls diffManager',
'yarn run version 1.2.3');
sinon.assert.calledOnce(shell.exec);

sinon.assert.calledOnce(assetCopier.copyAssets);
sinon.assert.calledWithExactly(assetCopier.copyAssets, siteProps.paths.docs, "1.2.3");
sinon.assert.calledOnce(assetCopier.copyDocAssets);
sinon.assert.calledWithExactly(assetCopier.copyDocAssets, siteProps.paths.docs, "1.2.3");

sinon.assert.calledOnce(linker.linkAssetsInMarkdownFiles);
sinon.assert.calledWithExactly(linker.linkAssetsInMarkdownFiles,
Expand Down Expand Up @@ -96,7 +96,7 @@ describe('createVersion does preliminary checks and calls diffManager',
sinon.assert.calledWithExactly(siteUtils.loadSiteProperties, SITE_DIR);
sinon.assert.notCalled(diffManager.generateFileDiff);
sinon.assert.notCalled(diffManager.cleanUpFileDiff);
sinon.assert.notCalled(assetCopier.copyAssets);
sinon.assert.notCalled(assetCopier.copyDocAssets);
sinon.assert.notCalled(linker.linkAssetsInMarkdownFiles);
sinon.assert.notCalled(shell.cd);
sinon.assert.notCalled(shell.exec);
Expand Down
Loading