-
-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
240 changed files
with
10,553 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# @astrojs/node | ||
|
||
This adapter allows Astro to deploy your SSR site to Node targets. | ||
|
||
## Documentation | ||
|
||
Read the [`@astrojs/node` docs][docs] | ||
|
||
## Support | ||
|
||
- Get help in the [Astro Discord][discord]. Post questions in our `#support` forum, or visit our dedicated `#dev` channel to discuss current development and more! | ||
|
||
- Check our [Astro Integration Documentation][astro-integration] for more on integrations. | ||
|
||
- Submit bug reports and feature requests as [GitHub issues][issues]. | ||
|
||
## Contributing | ||
|
||
This package is maintained by Astro's Core team. You're welcome to submit an issue or PR! These links will help you get started: | ||
|
||
- [Contributor Manual][contributing] | ||
- [Code of Conduct][coc] | ||
- [Community Guide][community] | ||
|
||
## License | ||
|
||
MIT | ||
|
||
Copyright (c) 2023–present [Astro][astro] | ||
|
||
[astro]: https://astro.build/ | ||
[docs]: https://docs.astro.build/en/guides/integrations-guide/node/ | ||
[contributing]: https://github.com/withastro/astro/blob/main/CONTRIBUTING.md | ||
[coc]: https://github.com/withastro/.github/blob/main/CODE_OF_CONDUCT.md | ||
[community]: https://github.com/withastro/.github/blob/main/COMMUNITY_GUIDE.md | ||
[discord]: https://astro.build/chat/ | ||
[issues]: https://github.com/withastro/astro/issues | ||
[astro-integration]: https://docs.astro.build/en/guides/integrations-guide/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
{ | ||
"name": "@astrojs/node", | ||
"description": "Deploy your site to a Node.js server", | ||
"version": "8.3.3", | ||
"type": "module", | ||
"types": "./dist/index.d.ts", | ||
"author": "withastro", | ||
"license": "MIT", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/withastro/adapters.git", | ||
"directory": "packages/node" | ||
}, | ||
"keywords": ["withastro", "astro-adapter"], | ||
"bugs": "https://github.com/withastro/adapters/issues", | ||
"homepage": "https://docs.astro.build/en/guides/integrations-guide/node/", | ||
"exports": { | ||
".": "./dist/index.js", | ||
"./server.js": "./dist/server.js", | ||
"./preview.js": "./dist/preview.js", | ||
"./package.json": "./package.json" | ||
}, | ||
"files": ["dist"], | ||
"scripts": { | ||
"build": "tsc", | ||
"test": "astro-scripts test \"test/**/*.test.js\"" | ||
}, | ||
"dependencies": { | ||
"send": "^0.18.0", | ||
"server-destroy": "^1.0.1" | ||
}, | ||
"peerDependencies": { | ||
"astro": "^4.2.0" | ||
}, | ||
"devDependencies": { | ||
"@astrojs/test-utils": "workspace:*", | ||
"@types/node": "^18.17.8", | ||
"@types/send": "^0.17.4", | ||
"@types/server-destroy": "^1.0.4", | ||
"astro": "^4.14.6", | ||
"astro-scripts": "workspace:*", | ||
"cheerio": "1.0.0", | ||
"express": "^4.19.2", | ||
"node-mocks-http": "^1.15.1" | ||
}, | ||
"publishConfig": { | ||
"provenance": true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import type { AstroAdapter, AstroIntegration } from 'astro'; | ||
import { AstroError } from 'astro/errors'; | ||
import type { Options, UserOptions } from './types.js'; | ||
|
||
export function getAdapter(options: Options): AstroAdapter { | ||
return { | ||
name: '@astrojs/node', | ||
serverEntrypoint: '@astrojs/node/server.js', | ||
previewEntrypoint: '@astrojs/node/preview.js', | ||
exports: ['handler', 'startServer', 'options'], | ||
args: options, | ||
supportedAstroFeatures: { | ||
hybridOutput: 'stable', | ||
staticOutput: 'stable', | ||
serverOutput: 'stable', | ||
assets: { | ||
supportKind: 'stable', | ||
isSharpCompatible: true, | ||
isSquooshCompatible: true, | ||
}, | ||
i18nDomains: 'experimental', | ||
envGetSecret: 'experimental', | ||
}, | ||
}; | ||
} | ||
|
||
// TODO: remove once we don't use a TLA anymore | ||
async function shouldExternalizeAstroEnvSetup() { | ||
try { | ||
await import('astro/env/setup'); | ||
return false; | ||
} catch { | ||
return true; | ||
} | ||
} | ||
|
||
export default function createIntegration(userOptions: UserOptions): AstroIntegration { | ||
if (!userOptions?.mode) { | ||
throw new AstroError(`Setting the 'mode' option is required.`); | ||
} | ||
|
||
let _options: Options; | ||
return { | ||
name: '@astrojs/node', | ||
hooks: { | ||
'astro:config:setup': async ({ updateConfig, config }) => { | ||
updateConfig({ | ||
image: { | ||
endpoint: config.image.endpoint ?? 'astro/assets/endpoint/node', | ||
}, | ||
vite: { | ||
ssr: { | ||
noExternal: ['@astrojs/node'], | ||
...((await shouldExternalizeAstroEnvSetup()) | ||
? { | ||
external: ['astro/env/setup'], | ||
} | ||
: {}), | ||
}, | ||
}, | ||
}); | ||
}, | ||
'astro:config:done': ({ setAdapter, config, logger }) => { | ||
_options = { | ||
...userOptions, | ||
client: config.build.client?.toString(), | ||
server: config.build.server?.toString(), | ||
host: config.server.host, | ||
port: config.server.port, | ||
assets: config.build.assets, | ||
}; | ||
setAdapter(getAdapter(_options)); | ||
|
||
if (config.output === 'static') { | ||
logger.warn( | ||
`\`output: "server"\` or \`output: "hybrid"\` is required to use this adapter.` | ||
); | ||
} | ||
}, | ||
}, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import type http from 'node:http'; | ||
import https from 'node:https'; | ||
import type { AddressInfo } from 'node:net'; | ||
import os from 'node:os'; | ||
import type { AstroIntegrationLogger } from 'astro'; | ||
import type { Options } from './types.js'; | ||
|
||
export async function logListeningOn( | ||
logger: AstroIntegrationLogger, | ||
server: http.Server | https.Server, | ||
options: Pick<Options, 'host'> | ||
) { | ||
await new Promise<void>((resolve) => server.once('listening', resolve)); | ||
const protocol = server instanceof https.Server ? 'https' : 'http'; | ||
// Allow to provide host value at runtime | ||
const host = getResolvedHostForHttpServer( | ||
process.env.HOST !== undefined && process.env.HOST !== '' ? process.env.HOST : options.host | ||
); | ||
const { port } = server.address() as AddressInfo; | ||
const address = getNetworkAddress(protocol, host, port); | ||
|
||
if (host === undefined) { | ||
logger.info( | ||
`Server listening on \n local: ${address.local[0]} \t\n network: ${address.network[0]}\n` | ||
); | ||
} else { | ||
logger.info(`Server listening on ${address.local[0]}`); | ||
} | ||
} | ||
|
||
function getResolvedHostForHttpServer(host: string | boolean) { | ||
if (host === false) { | ||
// Use a secure default | ||
return 'localhost'; | ||
// biome-ignore lint/style/noUselessElse: <explanation> | ||
} else if (host === true) { | ||
// If passed --host in the CLI without arguments | ||
return undefined; // undefined typically means 0.0.0.0 or :: (listen on all IPs) | ||
// biome-ignore lint/style/noUselessElse: <explanation> | ||
} else { | ||
return host; | ||
} | ||
} | ||
|
||
interface NetworkAddressOpt { | ||
local: string[]; | ||
network: string[]; | ||
} | ||
|
||
const wildcardHosts = new Set(['0.0.0.0', '::', '0000:0000:0000:0000:0000:0000:0000:0000']); | ||
|
||
// this code from vite https://github.com/vitejs/vite/blob/d09bbd093a4b893e78f0bbff5b17c7cf7821f403/packages/vite/src/node/utils.ts#L892-L914 | ||
export function getNetworkAddress( | ||
// biome-ignore lint/style/useDefaultParameterLast: <explanation> | ||
protocol: 'http' | 'https' = 'http', | ||
hostname: string | undefined, | ||
port: number, | ||
base?: string | ||
) { | ||
const NetworkAddress: NetworkAddressOpt = { | ||
local: [], | ||
network: [], | ||
}; | ||
// biome-ignore lint/complexity/noForEach: <explanation> | ||
Object.values(os.networkInterfaces()) | ||
.flatMap((nInterface) => nInterface ?? []) | ||
.filter( | ||
(detail) => | ||
// biome-ignore lint/complexity/useOptionalChain: <explanation> | ||
detail && | ||
detail.address && | ||
(detail.family === 'IPv4' || | ||
// @ts-expect-error Node 18.0 - 18.3 returns number | ||
detail.family === 4) | ||
) | ||
.forEach((detail) => { | ||
let host = detail.address.replace( | ||
'127.0.0.1', | ||
hostname === undefined || wildcardHosts.has(hostname) ? 'localhost' : hostname | ||
); | ||
// ipv6 host | ||
if (host.includes(':')) { | ||
host = `[${host}]`; | ||
} | ||
const url = `${protocol}://${host}:${port}${base ? base : ''}`; | ||
if (detail.address.includes('127.0.0.1')) { | ||
NetworkAddress.local.push(url); | ||
} else { | ||
NetworkAddress.network.push(url); | ||
} | ||
}); | ||
return NetworkAddress; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import type { NodeApp } from 'astro/app/node'; | ||
import { createAppHandler } from './serve-app.js'; | ||
import type { RequestHandler } from './types.js'; | ||
|
||
/** | ||
* Creates a middleware that can be used with Express, Connect, etc. | ||
* | ||
* Similar to `createAppHandler` but can additionally be placed in the express | ||
* chain as an error middleware. | ||
* | ||
* https://expressjs.com/en/guide/using-middleware.html#middleware.error-handling | ||
*/ | ||
export default function createMiddleware(app: NodeApp): RequestHandler { | ||
const handler = createAppHandler(app); | ||
const logger = app.getAdapterLogger(); | ||
// using spread args because express trips up if the function's | ||
// stringified body includes req, res, next, locals directly | ||
return async (...args) => { | ||
// assume normal invocation at first | ||
const [req, res, next, locals] = args; | ||
// short circuit if it is an error invocation | ||
if (req instanceof Error) { | ||
const error = req; | ||
if (next) { | ||
return next(error); | ||
// biome-ignore lint/style/noUselessElse: <explanation> | ||
} else { | ||
throw error; | ||
} | ||
} | ||
try { | ||
await handler(req, res, next, locals); | ||
} catch (err) { | ||
logger.error(`Could not render ${req.url}`); | ||
console.error(err); | ||
if (!res.headersSent) { | ||
// biome-ignore lint/style/noUnusedTemplateLiteral: <explanation> | ||
res.writeHead(500, `Server error`); | ||
res.end(); | ||
} | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { fileURLToPath } from 'node:url'; | ||
import type { CreatePreviewServer } from 'astro'; | ||
import { AstroError } from 'astro/errors'; | ||
import { logListeningOn } from './log-listening-on.js'; | ||
import type { createExports } from './server.js'; | ||
import { createServer } from './standalone.js'; | ||
|
||
type ServerModule = ReturnType<typeof createExports>; | ||
type MaybeServerModule = Partial<ServerModule>; | ||
|
||
const createPreviewServer: CreatePreviewServer = async (preview) => { | ||
let ssrHandler: ServerModule['handler']; | ||
let options: ServerModule['options']; | ||
try { | ||
process.env.ASTRO_NODE_AUTOSTART = 'disabled'; | ||
const ssrModule: MaybeServerModule = await import(preview.serverEntrypoint.toString()); | ||
if (typeof ssrModule.handler === 'function') { | ||
ssrHandler = ssrModule.handler; | ||
// biome-ignore lint/style/noNonNullAssertion: <explanation> | ||
options = ssrModule.options!; | ||
} else { | ||
throw new AstroError( | ||
`The server entrypoint doesn't have a handler. Are you sure this is the right file?` | ||
); | ||
} | ||
} catch (err) { | ||
if ((err as any).code === 'ERR_MODULE_NOT_FOUND') { | ||
throw new AstroError( | ||
`The server entrypoint ${fileURLToPath( | ||
preview.serverEntrypoint | ||
)} does not exist. Have you ran a build yet?` | ||
); | ||
// biome-ignore lint/style/noUselessElse: <explanation> | ||
} else { | ||
throw err; | ||
} | ||
} | ||
const host = preview.host ?? 'localhost'; | ||
const port = preview.port ?? 4321; | ||
const server = createServer(ssrHandler, host, port); | ||
|
||
// If user specified custom headers append a listener | ||
// to the server to add those headers to response | ||
if (preview.headers) { | ||
server.server.addListener('request', (_, res) => { | ||
if (res.statusCode === 200) { | ||
for (const [name, value] of Object.entries(preview.headers ?? {})) { | ||
if (value) res.setHeader(name, value); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
logListeningOn(preview.logger, server.server, options); | ||
await new Promise<void>((resolve, reject) => { | ||
server.server.once('listening', resolve); | ||
server.server.once('error', reject); | ||
server.server.listen(port, host); | ||
}); | ||
return server; | ||
}; | ||
|
||
export { createPreviewServer as default }; |
Oops, something went wrong.