Skip to content
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

support A3 projects with npm workspaces packages #4284

Merged
merged 18 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* Add `@apostrophecms/rich-text-widget:lint-fix-figure` task to wrap text nodes in paragraph tags when next to figure tag. Figure tags are not valid children of paragraph tags.
* Add `@apostrophecms/rich-text-widget:remove-empty-paragraph` task to remove empty paragraphs from all existing rich-texts.
* Add ability for custom tiptap extensions to access the options passed to rich text widgets at the area level
* Add support for [npm workspaces](https://docs.npmjs.com/cli/v10/configuring-npm/package-json#workspaces) dependencies. A workspace dependency can now be used as an Apostrophe module even if it is not a direct dependency of the Apostrophe project. Only direct workspaces dependencies of the Apostrophe project are supported, meaning this will only work with workspaces set in the Apostrophe project. Workspaces set in npm modules are not supported, please use [`bundle`](https://v3.docs.apostrophecms.org/reference/module-api/module-overview.html#bundle) instead. For instance, I have an Apostrophe project called `website`. `website` is set with two [npm workspaces](https://docs.npmjs.com/cli/v10/using-npm/workspaces), `workspace-a` & `workspace-b`. `workspace-a` `package.json` contains a module named `blog` as a dependency. `website` can reference `blog` as enabled in the Apostrophe `modules` configuration.
* The actual invocation of `renderPageForModule` by the `sendPage` method of all modules has been
factored out to `renderPage`, which is no longer deprecated. This provides a convenient override point
for those who wish to substitute something else for Nunjucks or just wrap the HTML in a larger data
Expand Down
38 changes: 35 additions & 3 deletions lib/moog-require.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,13 @@ module.exports = function(options) {
const initialFolder = path.dirname(self.root.filename);
let folder = initialFolder;
while (true) {
const file = `${folder}/package.json`;
const file = path.resolve(folder, 'package.json');
if (fs.existsSync(file)) {
const info = JSON.parse(fs.readFileSync(file, 'utf8'));
self.validPackages = new Set([ ...Object.keys(info.dependencies || {}), ...Object.keys(info.devDependencies || {}) ]);
self.validPackages = new Set([
...getDependencies(info),
...getWorkspacesDependencies(folder)(info)
]);
break;
} else {
folder = path.dirname(folder);
Expand Down Expand Up @@ -231,7 +234,36 @@ module.exports = function(options) {
}
self.symlinksCache.set(type, link);
return link;
};
}

function getDependencies({
dependencies = {},
devDependencies = {}
} = {}) {
return [
...Object.keys(dependencies || {}),
...Object.keys(devDependencies || {})
];
}

function getWorkspacesDependencies(folder) {
return ({ workspaces = [] } = {}) => {
if (workspaces.length === 0) {
return [];
}

// Ternary is required because glob expects at least 2 entries when using curly braces
const pattern = workspaces.length === 1 ? workspaces[0] : `{${workspaces.join(',')}}`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty sure the join call is always sufficient here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a comment, glob expects at least two entries when using curly braces. Silly bug!

const packagePath = path.resolve(folder, pattern, 'package.json');
const workspacePackages = glob.sync(packagePath, { follow: true });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

niiice

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very slick use of advanced glob features


return workspacePackages.flatMap(packagePath => {
const info = JSON.parse(fs.readFileSync(packagePath, 'utf8'));

return getDependencies(info);
});
};
}

self.isImprovement = function(name) {
return _.has(self.improvements, name);
Expand Down
38 changes: 38 additions & 0 deletions test/workspaces-project.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const assert = require('node:assert').strict;
const util = require('node:util');
const { exec } = require('node:child_process');
const path = require('node:path');
const t = require('../test-lib/test.js');
const app = require('./workspaces-project/app.js');

describe('workspaces dependencies', function() {
this.timeout(t.timeout);

before(async function() {
await util.promisify(exec)('npm install', { cwd: path.resolve(process.cwd(), 'test/workspaces-project') });
});

it('should allow workspaces dependency in the project', async function() {
let apos;

try {
apos = await t.create(app);
const { server } = apos.modules['@apostrophecms/express'];
const { address, port } = server.address();

const actual = apos.util.logger.getMessages();
const expected = {
debug: [],
info: [ `Listening at http://${address}:${port}` ],
warn: [],
error: []
};

assert.deepEqual(actual, expected);
} catch (error) {
assert.fail('Should have found @apostrophecms/sitemap hidden in workspace-a as a valid dependency. '.concat(error.message));
} finally {
apos && await t.destroy(apos);
}
});
});
16 changes: 16 additions & 0 deletions test/workspaces-project/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module.exports = {
root: module,
shortName: 'workspaces-project',
modules: {
'@apostrophecms/express': {
options: {
address: '127.0.0.1'
}
},
'@apostrophecms/sitemap': {
options: {
baseUrl: 'http://localhost:3000'
}
}
}
};
40 changes: 40 additions & 0 deletions test/workspaces-project/modules/@apostrophecms/log/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const createLogger = () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good technique.

const messages = {
debug: [],
info: [],
warn: [],
error: []
};

return {
debug: (...args) => {
console.debug(...args);
messages.debug.push(...args);
},
info: (...args) => {
console.info(...args);
messages.info.push(...args);
},
warn: (...args) => {
console.warn(...args);
messages.warn.push(...args);
},
error: (...args) => {
console.error(...args);
messages.error.push(...args);
},
destroy: () => {
delete messages.debug;
delete messages.info;
delete messages.warn;
delete messages.error;
},
getMessages: () => messages
};
};

module.exports = {
options: {
logger: createLogger()
}
};
18 changes: 18 additions & 0 deletions test/workspaces-project/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "workspace-project",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"apostrophe": "file:../../."
},
"workspaces": [
"workspace-a"
]
}
15 changes: 15 additions & 0 deletions test/workspaces-project/workspace-a/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "workspace-a",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@apostrophecms/sitemap": "^1.0.2"
}
}
Loading