Skip to content

Commit 4019132

Browse files
committed
⚗️(front) conditionaly install blocknote xl packages
The blocknote xl packages are licensed under AGPL v3. We want to allow a reuser to build the project without them. For this, in a first step we don't list these packages in the dependencies but install them using a postinstall script. This script is a node script looking in every package.json files under the apps directory, look if an `extraDependencies` key exists and if yes hten will install the listed dependencies after prompting the user if wants or not to install them. Also an environment variable AUTO_INSTALL_EXTRA_DEPS can be used to explicitly install all the dependencies or to explicitly not install them. This will help to build an image with and without these dependencies. Moreover we also need thisin a CI context.
1 parent c11d59c commit 4019132

File tree

7 files changed

+2748
-2665
lines changed

7 files changed

+2748
-2665
lines changed

.github/workflows/dependencies.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
node-version: ${{ inputs.node_version }}
3535
- name: Install dependencies
3636
if: steps.front-node_modules.outputs.cache-hit != 'true'
37-
run: cd src/frontend/ && yarn install --frozen-lockfile
37+
run: cd src/frontend/ && AUTO_INSTALL_EXTRA_DEPS=true yarn install --frozen-lockfile
3838
- name: Cache install frontend
3939
if: steps.front-node_modules.outputs.cache-hit != 'true'
4040
uses: actions/cache@v4

docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ services:
158158
Y_PROVIDER_URL: "ws://localhost:4444"
159159
MEDIA_URL: "http://localhost:8083"
160160
SW_DEACTIVATED: "true"
161+
AUTO_INSTALL_EXTRA_DEPS: "true"
161162
image: impress:frontend-development
162163
ports:
163164
- "3000:3000"

src/frontend/Dockerfile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
FROM node:20-alpine AS frontend-deps
22

3+
ARG AUTO_INSTALL_EXTRA_DEPS=false
4+
35
WORKDIR /home/frontend/
46

57
COPY ./src/frontend/package.json ./package.json
8+
COPY ./src/frontend/bin/conditional-install.js ./bin/conditional-install.js
69
COPY ./src/frontend/yarn.lock ./yarn.lock
710
COPY ./src/frontend/apps/impress/package.json ./apps/impress/package.json
811
COPY ./src/frontend/packages/eslint-config-impress/package.json ./packages/eslint-config-impress/package.json
912

10-
RUN yarn install --frozen-lockfile
13+
RUN AUTO_INSTALL_EXTRA_DEPS=${AUTO_INSTALL_EXTRA_DEPS} yarn install --frozen-lockfile
1114

1215
COPY .dockerignore ./.dockerignore
1316
COPY ./src/frontend/.prettierrc.js ./.prettierrc.js

src/frontend/apps/impress/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919
"@blocknote/core": "0.23.2-hotfix.0",
2020
"@blocknote/mantine": "0.23.2-hotfix.0",
2121
"@blocknote/react": "0.23.2-hotfix.0",
22-
"@blocknote/xl-docx-exporter": "0.23.2-hotfix.0",
23-
"@blocknote/xl-pdf-exporter": "0.23.2-hotfix.0",
2422
"@fontsource/material-icons": "5.2.5",
2523
"@gouvfr-lasuite/integration": "1.0.2",
2624
"@gouvfr-lasuite/ui-kit": "0.1.3",
@@ -79,5 +77,9 @@
7977
"typescript": "*",
8078
"webpack": "5.98.0",
8179
"workbox-webpack-plugin": "7.1.0"
80+
},
81+
"extraDependencies": {
82+
"@blocknote/xl-docx-exporter": "0.23.2-hotfix.0",
83+
"@blocknote/xl-pdf-exporter": "0.23.2-hotfix.0"
8284
}
8385
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#!/usr/bin/env node
2+
3+
const fs = require('fs');
4+
const path = require('path');
5+
const { execSync } = require('child_process');
6+
const readline = require('readline');
7+
8+
// Check if AUTO_INSTALL_EXTRA_DEPS is explicitly set to false
9+
if (process.env.AUTO_INSTALL_EXTRA_DEPS === 'false') {
10+
console.log('AUTO_INSTALL_EXTRA_DEPS is set to false, skipping script execution');
11+
process.exit(0);
12+
}
13+
14+
// Get the workspace root directory
15+
const workspaceRoot = process.cwd();
16+
17+
// Check if AUTO_INSTALL_EXTRA_DEPS environment variable is set to bypass prompts
18+
const autoMode = process.env.AUTO_INSTALL_EXTRA_DEPS === 'true';
19+
20+
// Create readline interface for user prompts
21+
const rl = readline.createInterface({
22+
input: process.stdin,
23+
output: process.stdout
24+
});
25+
26+
// Function to prompt user for confirmation
27+
function promptUser(question) {
28+
return new Promise((resolve) => {
29+
rl.question(question, (answer) => {
30+
resolve(answer.toLowerCase().startsWith('y'));
31+
});
32+
});
33+
}
34+
35+
// Function to find all package.json files in the apps directory
36+
function findPackageJsonFiles(directory) {
37+
const appsDir = path.join(workspaceRoot, directory);
38+
39+
// Check if the directory exists
40+
if (!fs.existsSync(appsDir)) {
41+
console.log(`Directory ${directory} does not exist, skipping...`);
42+
return [];
43+
}
44+
45+
const packageJsonFiles = [];
46+
47+
// Read all items in the directory
48+
const items = fs.readdirSync(appsDir);
49+
50+
for (const item of items) {
51+
const itemPath = path.join(appsDir, item);
52+
const stat = fs.statSync(itemPath);
53+
54+
if (stat.isDirectory()) {
55+
// Check if this directory has a package.json
56+
const packageJsonPath = path.join(itemPath, 'package.json');
57+
if (fs.existsSync(packageJsonPath)) {
58+
packageJsonFiles.push(packageJsonPath);
59+
// Skip searching in subdirectories once a package.json is found
60+
continue;
61+
}
62+
63+
// Only search subdirectories if no package.json was found in this directory
64+
packageJsonFiles.push(...findPackageJsonFiles(path.join(directory, item)));
65+
}
66+
}
67+
68+
return packageJsonFiles;
69+
}
70+
71+
// Find all package.json files in the apps directory
72+
const packageJsonFiles = findPackageJsonFiles('apps');
73+
74+
if (packageJsonFiles.length === 0) {
75+
console.log('No package.json files found in the apps directory');
76+
process.exit(0);
77+
}
78+
79+
console.log(`Found ${packageJsonFiles.length} package.json files in the apps directory`);
80+
81+
// Process each package.json file
82+
async function processPackageJsonFiles() {
83+
for (const packageJsonPath of packageJsonFiles) {
84+
try {
85+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
86+
87+
// Check if extraDependencies exists
88+
if (packageJson.extraDependencies && Object.keys(packageJson.extraDependencies).length > 0) {
89+
console.log(`Found extraDependencies in ${packageJsonPath}, installing...`);
90+
91+
// Process each dependency individually
92+
for (const [pkg, version] of Object.entries(packageJson.extraDependencies)) {
93+
const dependencyToInstall = `${pkg}@${version}`;
94+
95+
// Prompt user if not in auto mode
96+
let shouldInstall = autoMode;
97+
if (!autoMode) {
98+
shouldInstall = await promptUser(`
99+
Do you want to install ${dependencyToInstall}? (y/n):
100+
Note that these packages are dual-licensed by Blocknotejs
101+
under AGPL-3.0 or a proprietary license. If you choose
102+
to install them, please ensure you fulfill your licensing
103+
obligations with respect to BlockNoteJS
104+
`);
105+
}
106+
107+
if (shouldInstall) {
108+
// Install the dependency using npm install
109+
try {
110+
console.log(`Installing: ${dependencyToInstall}`);
111+
execSync(`npm install --no-save --ignore-scripts --no-audit ${dependencyToInstall}`, { stdio: 'inherit' });
112+
console.log(`Extra dependency ${dependencyToInstall} installed successfully`);
113+
} catch (error) {
114+
console.error(`Failed to install extra dependency ${dependencyToInstall}:`, error.message);
115+
// Continue with other dependencies even if one fails
116+
}
117+
} else {
118+
console.log(`Skipping installation of ${dependencyToInstall}`);
119+
}
120+
}
121+
} else {
122+
console.log(`No extraDependencies found in ${packageJsonPath}, skipping installation`);
123+
}
124+
} catch (error) {
125+
console.error(`Error reading or parsing ${packageJsonPath}:`, error.message);
126+
// Continue with other package.json files even if one fails
127+
}
128+
}
129+
130+
console.log('Finished processing all package.json files');
131+
rl.close();
132+
}
133+
134+
// Run the async function
135+
processPackageJsonFiles().catch(error => {
136+
console.error('An error occurred:', error);
137+
rl.close();
138+
process.exit(1);
139+
});

src/frontend/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"i18n:deploy": "yarn I18N run format-deploy && yarn APP_IMPRESS prettier",
2626
"i18n:test": "yarn I18N run test",
2727
"test": "yarn server:test && yarn app:test",
28-
"server:test": "yarn COLLABORATION_SERVER run test"
28+
"server:test": "yarn COLLABORATION_SERVER run test",
29+
"postinstall": "node ./bin/conditional-install.js"
2930
},
3031
"resolutions": {
3132
"@types/node": "22.13.9",

0 commit comments

Comments
 (0)