New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@greenwood/cli

Package Overview
Dependencies
Maintainers
1
Versions
161
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@greenwood/cli - npm Package Compare versions

Comparing version 0.28.1 to 0.28.2

src/lib/api-route-worker.js

4

package.json
{
"name": "@greenwood/cli",
"version": "0.28.1",
"version": "0.28.2",
"description": "Greenwood CLI.",

@@ -76,3 +76,3 @@ "type": "module",

},
"gitHead": "9686fc1c6a742d14e05c08a7a2bbd70d4a5f8a1b"
"gitHead": "65dce26d99422cf7451a5a8b0d1d97dad5e15c6b"
}
import { bundleCompilation } from '../lifecycles/bundle.js';
import { checkResourceExists } from '../lib/resource-utils.js';
import { checkResourceExists, trackResourcesForRoute } from '../lib/resource-utils.js';
import { copyAssets } from '../lifecycles/copy.js';

@@ -8,2 +8,58 @@ import fs from 'fs/promises';

// TODO a lot of these are duplicated in the prerender lifecycle too
// would be good to refactor
async function servePage(url, request, plugins) {
let response = new Response('');
for (const plugin of plugins) {
if (plugin.shouldServe && await plugin.shouldServe(url, request)) {
response = await plugin.serve(url, request);
break;
}
}
return response;
}
async function interceptPage(url, request, plugins, body) {
let response = new Response(body, {
headers: new Headers({ 'Content-Type': 'text/html' })
});
for (const plugin of plugins) {
if (plugin.shouldIntercept && await plugin.shouldIntercept(url, request, response)) {
response = await plugin.intercept(url, request, response);
}
}
return response;
}
function getPluginInstances (compilation) {
return [...compilation.config.plugins]
.filter(plugin => plugin.type === 'resource' && plugin.name !== 'plugin-node-modules:resource')
.map((plugin) => {
return plugin.provider(compilation);
});
}
// TODO does this make more sense in bundle lifecycle?
// https://github.com/ProjectEvergreen/greenwood/issues/970
// or could this be done sooner (like in appTemplate building in html resource plugin)?
// Or do we need to ensure userland code / plugins have gone first
async function trackResourcesForRoutes(compilation) {
const plugins = getPluginInstances(compilation);
for (const page of compilation.graph) {
const { route } = page;
const url = new URL(`http://localhost:${compilation.config.port}${route}`);
const request = new Request(url);
let body = await (await servePage(url, request, plugins)).text();
body = await (await interceptPage(url, request, plugins, body)).text();
await trackResourcesForRoute(body, compilation, route);
}
}
const runProductionBuild = async (compilation) => {

@@ -47,2 +103,3 @@

if (prerenderPlugin.workerUrl) {
await trackResourcesForRoutes(compilation);
await preRenderCompilationWorker(compilation, prerenderPlugin);

@@ -53,2 +110,3 @@ } else {

} else {
await trackResourcesForRoutes(compilation);
await staticRenderCompilation(compilation);

@@ -55,0 +113,0 @@ }

import fs from 'fs/promises';
import { hashString } from './hashing-utils.js';
import htmlparser from 'node-html-parser';

@@ -108,2 +109,65 @@ async function modelResource(context, type, src = undefined, contents = undefined, optimizationAttr = undefined, rawAttributes = undefined) {

// TODO does this make more sense in bundle lifecycle?
// https://github.com/ProjectEvergreen/greenwood/issues/970
// or could this be done sooner (like in appTemplate building in html resource plugin)?
// Or do we need to ensure userland code / plugins have gone first
// before we can curate the final list of <script> / <style> / <link> tags to bundle
async function trackResourcesForRoute(html, compilation, route) {
const { context } = compilation;
const root = htmlparser.parse(html, {
script: true,
style: true
});
// intentionally support <script> tags from the <head> or <body>
const scripts = await Promise.all(root.querySelectorAll('script')
.filter(script => (
isLocalLink(script.getAttribute('src')) || script.rawText)
&& script.rawAttrs.indexOf('importmap') < 0)
.map(async(script) => {
const src = script.getAttribute('src');
const optimizationAttr = script.getAttribute('data-gwd-opt');
const { rawAttrs } = script;
if (src) {
// <script src="...."></script>
return await modelResource(context, 'script', src, null, optimizationAttr, rawAttrs);
} else if (script.rawText) {
// <script>...</script>
return await modelResource(context, 'script', null, script.rawText, optimizationAttr, rawAttrs);
}
}));
const styles = await Promise.all(root.querySelectorAll('style')
.filter(style => !(/\$/).test(style.rawText) && !(/<!-- Shady DOM styles for -->/).test(style.rawText)) // filter out Shady DOM <style> tags that happen when using puppeteer
.map(async(style) => await modelResource(context, 'style', null, style.rawText, null, style.getAttribute('data-gwd-opt'))));
const links = await Promise.all(root.querySelectorAll('head link')
.filter(link => {
// <link rel="stylesheet" href="..."></link>
return link.getAttribute('rel') === 'stylesheet'
&& link.getAttribute('href') && isLocalLink(link.getAttribute('href'));
}).map(async(link) => {
return modelResource(context, 'link', link.getAttribute('href'), null, link.getAttribute('data-gwd-opt'), link.rawAttrs);
}));
const resources = [
...scripts,
...styles,
...links
];
resources.forEach(resource => {
compilation.resources.set(resource.sourcePathURL.pathname, resource);
});
compilation.graph.find(page => page.route === route).resources = resources.map(resource => resource.sourcePathURL.pathname);
return resources;
}
function isLocalLink(url = '') {
return url !== '' && (url.indexOf('http') !== 0 && url.indexOf('//') !== 0);
}
export {

@@ -114,3 +178,4 @@ checkResourceExists,

normalizePathnameForWindows,
resolveForRelativeUrl
resolveForRelativeUrl,
trackResourcesForRoute
};

@@ -11,3 +11,3 @@ /* eslint-disable max-depth, max-len */

const { outputDir } = compilation.context;
const { resources } = compilation;
const { resources, graph } = compilation;

@@ -27,2 +27,4 @@ // https://stackoverflow.com/a/56150320/417806

}));
await fs.writeFile(new URL('./graph.json', outputDir), JSON.stringify(graph));
}

@@ -187,12 +189,3 @@

const input = [];
// TODO ideally be able to serialize entire graph (or only an explicit subset?)
// right now page.imports is breaking JSON.stringify
// https://github.com/ProjectEvergreen/greenwood/issues/1008
const intermediateGraph = compilation.graph.map(page => {
const p = { ...page };
delete p.imports;
return p;
});
if (!compilation.config.prerender) {

@@ -213,3 +206,3 @@ for (const page of compilation.graph) {

const htmlOptimizer = compilation.config.plugins.find(plugin => plugin.name === 'plugin-standard-html').provider(compilation);
let body = 'Hello from the ${page.id} page!';
let body = '';
let html = '';

@@ -262,3 +255,3 @@ let frontmatter;

moduleUrl: routeModuleLocationUrl.href,
compilation: \`${JSON.stringify({ graph: intermediateGraph })}\`,
compilation: \`${JSON.stringify(compilation)}\`,
route: '${pagePath}'

@@ -319,8 +312,2 @@ });

});
// centrally register all static resources
compilation.graph.map((page) => {
return page.imports;
}).flat().forEach(resource => {
compilation.resources.set(resource.sourcePathURL.pathname, resource);
});

@@ -327,0 +314,0 @@ console.info('bundling static assets...');

@@ -45,11 +45,2 @@ import { checkResourceExists } from '../lib/resource-utils.js';

// hydrate URLs
compilation.graph.forEach((page, idx) => {
if (page.imports.length > 0) {
page.imports.forEach((imp, jdx) => {
compilation.graph[idx].imports[jdx].sourcePathURL = new URL(imp.sourcePathURL);
});
}
});
if (await checkResourceExists(new URL('./manifest.json', outputDir))) {

@@ -56,0 +47,0 @@ console.info('Loading manifest from build output...');

/* eslint-disable complexity, max-depth */
import fs from 'fs/promises';
import fm from 'front-matter';
import { checkResourceExists, modelResource } from '../lib/resource-utils.js';
import { checkResourceExists } from '../lib/resource-utils.js';
import toc from 'markdown-toc';

@@ -22,3 +22,4 @@ import { Worker } from 'worker_threads';

data: {},
imports: []
imports: [],
resources: []
}];

@@ -129,9 +130,3 @@

if (result.frontmatter) {
const resources = await Promise.all((result.frontmatter.imports || []).map(async (resource) => {
const type = resource.split('.').pop() === 'js' ? 'script' : 'link';
return await modelResource(compilation.context, type, resource);
}));
result.frontmatter.imports = resources;
result.frontmatter.imports = result.frontmatter.imports || [];
ssrFrontmatter = result.frontmatter;

@@ -183,3 +178,4 @@ }

* label: "pretty" text representation of the filename
* imports: per page JS or CSS file imports to be included in HTML output
* imports: per page JS or CSS file imports to be included in HTML output from frontmatter
* resources: sum of all resources for the entire page
* outputPath: the filename to write to when generating static HTML

@@ -200,2 +196,3 @@ * path: path to the file relative to the workspace

imports,
resources: [],
outputPath: route === '/404/'

@@ -313,2 +310,3 @@ ? '404.html'

imports: [],
resources: [],
outputPath: `${node.route}index.html`,

@@ -315,0 +313,0 @@ ...node,

import fs from 'fs/promises';
import htmlparser from 'node-html-parser';
import { checkResourceExists, modelResource } from '../lib/resource-utils.js';
import { checkResourceExists, trackResourcesForRoute } from '../lib/resource-utils.js';
import os from 'os';
import { WorkerPool } from '../lib/threadpool.js';
function isLocalLink(url = '') {
return url !== '' && (url.indexOf('http') !== 0 && url.indexOf('//') !== 0);
}
// TODO a lot of these are duplicated in the build lifecycle too
// would be good to refactor
async function createOutputDirectory(route, outputDir) {

@@ -19,57 +16,2 @@ if (route !== '/404/' && !await checkResourceExists(outputDir)) {

// TODO does this make more sense in bundle lifecycle?
// https://github.com/ProjectEvergreen/greenwood/issues/970
// or could this be done sooner (like in appTemplate building in html resource plugin)?
// Or do we need to ensure userland code / plugins have gone first
// before we can curate the final list of <script> / <style> / <link> tags to bundle
async function trackResourcesForRoute(html, compilation, route) {
const { context } = compilation;
const root = htmlparser.parse(html, {
script: true,
style: true
});
// intentionally support <script> tags from the <head> or <body>
const scripts = await Promise.all(root.querySelectorAll('script')
.filter(script => (
isLocalLink(script.getAttribute('src')) || script.rawText)
&& script.rawAttrs.indexOf('importmap') < 0)
.map(async(script) => {
const src = script.getAttribute('src');
const optimizationAttr = script.getAttribute('data-gwd-opt');
const { rawAttrs } = script;
if (src) {
// <script src="...."></script>
return await modelResource(context, 'script', src, null, optimizationAttr, rawAttrs);
} else if (script.rawText) {
// <script>...</script>
return await modelResource(context, 'script', null, script.rawText, optimizationAttr, rawAttrs);
}
}));
const styles = await Promise.all(root.querySelectorAll('style')
.filter(style => !(/\$/).test(style.rawText) && !(/<!-- Shady DOM styles for -->/).test(style.rawText)) // filter out Shady DOM <style> tags that happen when using puppeteer
.map(async(style) => await modelResource(context, 'style', null, style.rawText, null, style.getAttribute('data-gwd-opt'))));
const links = await Promise.all(root.querySelectorAll('head link')
.filter(link => {
// <link rel="stylesheet" href="..."></link>
return link.getAttribute('rel') === 'stylesheet'
&& link.getAttribute('href') && isLocalLink(link.getAttribute('href'));
}).map(async(link) => {
return modelResource(context, 'link', link.getAttribute('href'), null, link.getAttribute('data-gwd-opt'), link.rawAttrs);
}));
const resources = [
...scripts,
...styles,
...links
];
compilation.graph.find(page => page.route === route).imports = resources;
return resources;
}
async function servePage(url, request, plugins) {

@@ -120,3 +62,3 @@ let response = new Response('');

for (const page of pages) {
const { route, outputPath } = page;
const { route, outputPath, resources } = page;
const outputDirUrl = new URL(`./${route}/`, scratchDir);

@@ -132,4 +74,4 @@ const outputPathUrl = new URL(`./${outputPath}`, scratchDir);

const resources = await trackResourcesForRoute(body, compilation, route);
const scripts = resources
.map(resource => compilation.resources.get(resource))
.filter(resource => resource.type === 'script')

@@ -206,3 +148,2 @@ .map(resource => resource.sourcePathURL.href);

await trackResourcesForRoute(body, compilation, route);
await createOutputDirectory(route, outputDirUrl);

@@ -209,0 +150,0 @@ await fs.writeFile(outputPathUrl, body);

import fs from 'fs/promises';
import { hashString } from '../lib/hashing-utils.js';
import Koa from 'koa';
import { mergeResponse } from '../lib/resource-utils.js';
import { checkResourceExists, mergeResponse } from '../lib/resource-utils.js';
import { Readable } from 'stream';

@@ -230,30 +230,33 @@ import { ResourceInterface } from '../lib/resource-interface.js';

const url = new URL(`.${ctx.url}`, outputDir.href);
const resourcePlugins = standardResourcePlugins
.filter((plugin) => plugin.isStandardStaticResource)
.map((plugin) => {
return plugin.provider(compilation);
if (await checkResourceExists(url)) {
const resourcePlugins = standardResourcePlugins
.filter((plugin) => plugin.isStandardStaticResource)
.map((plugin) => {
return plugin.provider(compilation);
});
const request = new Request(url.href, {
headers: new Headers(ctx.request.header)
});
const initResponse = new Response(ctx.body, {
status: ctx.response.status,
headers: new Headers(ctx.response.header)
});
const response = await resourcePlugins.reduce(async (responsePromise, plugin) => {
return plugin.shouldServe && await plugin.shouldServe(url, request)
? Promise.resolve(await plugin.serve(url, request))
: responsePromise;
}, Promise.resolve(initResponse));
const request = new Request(url.href, {
headers: new Headers(ctx.request.header)
});
const initResponse = new Response(ctx.body, {
status: ctx.response.status,
headers: new Headers(ctx.response.header)
});
const response = await resourcePlugins.reduce(async (responsePromise, plugin) => {
return plugin.shouldServe && await plugin.shouldServe(url, request)
? Promise.resolve(await plugin.serve(url, request))
: responsePromise;
}, Promise.resolve(initResponse));
if (response.ok) {
ctx.body = Readable.from(response.body);
ctx.type = response.headers.get('Content-Type');
ctx.status = response.status;
if (response.ok) {
ctx.body = Readable.from(response.body);
ctx.type = response.headers.get('Content-Type');
ctx.status = response.status;
// TODO automatically loop and apply all custom headers to Koa response, include Content-Type below
// https://github.com/ProjectEvergreen/greenwood/issues/1048
if (response.headers.has('Content-Length')) {
ctx.set('Content-Length', response.headers.get('Content-Length'));
// TODO automatically loop and apply all custom headers to Koa response, include Content-Type below
// https://github.com/ProjectEvergreen/greenwood/issues/1048
if (response.headers.has('Content-Length')) {
ctx.set('Content-Length', response.headers.get('Content-Length'));
}
}

@@ -260,0 +263,0 @@ }

@@ -7,3 +7,34 @@ /*

import { ResourceInterface } from '../../lib/resource-interface.js';
import { Worker } from 'worker_threads';
// https://stackoverflow.com/questions/57447685/how-can-i-convert-a-request-object-into-a-stringifiable-object-in-javascript
function requestAsObject (request) {
if (!request instanceof Request) {
throw Object.assign(
new Error(),
{ name: 'TypeError', message: 'Argument must be a Request object' }
);
}
request = request.clone();
function stringifiableObject (obj) {
const filtered = {};
for (const key in obj) {
if (['boolean', 'number', 'string'].includes(typeof obj[key]) || obj[key] === null) {
filtered[key] = obj[key];
}
}
return filtered;
}
// TODO handle full response
// https://github.com/ProjectEvergreen/greenwood/issues/1048
return {
...stringifiableObject(request),
headers: Object.fromEntries(request.headers),
signal: stringifiableObject(request.signal)
// bodyText: await request.text(), // requires function to be async
};
}
class ApiRoutesResource extends ResourceInterface {

@@ -23,13 +54,36 @@ constructor(compilation, options) {

const apiUrl = new URL(`.${api.path}`, this.compilation.context.userWorkspace);
// https://github.com/nodejs/modules/issues/307#issuecomment-1165387383
const href = process.env.__GWD_COMMAND__ === 'develop' // eslint-disable-line no-underscore-dangle
? `${apiUrl.href}?t=${Date.now()}`
: apiUrl.href;
const { handler } = await import(href);
const req = new Request(new URL(`${request.url.origin}${url}`), {
const href = apiUrl.href;
const req = new Request(new URL(url), {
...request
});
return await handler(req);
// TODO does this ever run in anything but development mode?
if (process.env.__GWD_COMMAND__ === 'develop') { // eslint-disable-line no-underscore-dangle
const workerUrl = new URL('../../lib/api-route-worker.js', import.meta.url);
const response = await new Promise((resolve, reject) => {
const worker = new Worker(workerUrl);
const req = requestAsObject(request);
worker.on('message', (result) => {
resolve(result);
});
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0) {
reject(new Error(`Worker stopped with exit code ${code}`));
}
});
worker.postMessage({ href, request: req });
});
return new Response(response.body, {
...response
});
} else {
const { handler } = await import(href);
return await handler(req);
}
}

@@ -36,0 +90,0 @@ }

@@ -221,7 +221,7 @@ /* eslint-disable complexity, max-depth */

const { pathname } = url;
const pageResources = this.compilation.graph.find(page => page.outputPath === pathname || page.route === pathname).imports;
const pageResources = this.compilation.graph.find(page => page.outputPath === pathname || page.route === pathname).resources;
let body = await response.text();
for (const pageResource of pageResources) {
const keyedResource = this.compilation.resources.get(pageResource.sourcePathURL.pathname);
const keyedResource = this.compilation.resources.get(pageResource);
const { contents, src, type, optimizationAttr, optimizedFileContents, optimizedFileName, rawAttributes } = keyedResource;

@@ -228,0 +228,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc