aboutsummaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rw-r--r--scripts/CHANGELOG.md100
-rw-r--r--scripts/cmd/build.js175
-rw-r--r--scripts/cmd/prebuild.js117
-rw-r--r--scripts/cmd/test.js89
-rw-r--r--scripts/deps/update-example-versions.js78
-rwxr-xr-xscripts/index.js24
-rw-r--r--scripts/jsconfig.json12
-rw-r--r--scripts/package.json17
-rw-r--r--scripts/smoke/check.js97
9 files changed, 709 insertions, 0 deletions
diff --git a/scripts/CHANGELOG.md b/scripts/CHANGELOG.md
new file mode 100644
index 000000000..d847e16f3
--- /dev/null
+++ b/scripts/CHANGELOG.md
@@ -0,0 +1,100 @@
+# astro-scripts
+
+## 0.0.14
+
+### Patch Changes
+
+- Updated dependencies [[`afbbc4d5b`](https://github.com/withastro/astro/commit/afbbc4d5bfafc1779bac00b41c2a1cb1c90f2808)]:
+ - @astrojs/webapi@2.1.0
+
+## 0.0.13
+
+### Patch Changes
+
+- Updated dependencies [[`0abd1d3e4`](https://github.com/withastro/astro/commit/0abd1d3e42cf7bf5efb8c41f37e011b933fb0629)]:
+ - @astrojs/webapi@2.0.3
+
+## 0.0.12
+
+### Patch Changes
+
+- Updated dependencies [[`5aa6580f7`](https://github.com/withastro/astro/commit/5aa6580f775405a4443835bf7eb81f0c65e5aed6)]:
+ - @astrojs/webapi@2.0.2
+
+## 0.0.11
+
+### Patch Changes
+
+- Updated dependencies [[`bb1801013`](https://github.com/withastro/astro/commit/bb1801013708d9efdbbcebc53a564ac375bf4b26)]:
+ - @astrojs/webapi@2.0.1
+
+## 0.0.10
+
+### Patch Changes
+
+- Updated dependencies [[`46ecd5de3`](https://github.com/withastro/astro/commit/46ecd5de34df619e2ee73ccea39a57acd37bc0b8), [`c55fbcb8e`](https://github.com/withastro/astro/commit/c55fbcb8edca1fe118a44f68c9f9436a4719d171), [`1f92d64ea`](https://github.com/withastro/astro/commit/1f92d64ea35c03fec43aff64eaf704dc5a9eb30a)]:
+ - @astrojs/webapi@2.0.0
+
+## 0.0.10-beta.1
+
+<details>
+<summary>See changes in 0.0.10-beta.1</summary>
+
+### Patch Changes
+
+- Updated dependencies [[`46ecd5de3`](https://github.com/withastro/astro/commit/46ecd5de34df619e2ee73ccea39a57acd37bc0b8)]:
+ - @astrojs/webapi@2.0.0-beta.1
+
+</details>
+
+## 0.0.10-beta.0
+
+<details>
+<summary>See changes in 0.0.10-beta.0</summary>
+
+### Patch Changes
+
+- Updated dependencies [[`c55fbcb8e`](https://github.com/withastro/astro/commit/c55fbcb8edca1fe118a44f68c9f9436a4719d171), [`1f92d64ea`](https://github.com/withastro/astro/commit/1f92d64ea35c03fec43aff64eaf704dc5a9eb30a)]:
+ - @astrojs/webapi@2.0.0-beta.0
+
+</details>
+
+## 0.0.9
+
+### Patch Changes
+
+- Updated dependencies [[`b6a478f37`](https://github.com/withastro/astro/commit/b6a478f37648491321077750bfca7bddf3cafadd)]:
+ - @astrojs/webapi@1.1.1
+
+## 0.0.8
+
+### Patch Changes
+
+- Updated dependencies [[`5e4c5252b`](https://github.com/withastro/astro/commit/5e4c5252bd80cbaf6a7ee4d4503ece007664410f)]:
+ - @astrojs/webapi@1.1.0
+
+## 0.0.7
+
+### Patch Changes
+
+- Updated dependencies [[`04ad44563`](https://github.com/withastro/astro/commit/04ad445632c67bdd60c1704e1e0dcbcaa27b9308)]:
+ - @astrojs/webapi@1.0.0
+
+## 0.0.6
+
+### Patch Changes
+
+- [#3758](https://github.com/withastro/astro/pull/3758) [`660abd3d`](https://github.com/withastro/astro/commit/660abd3deeb3c451ce32d8d0d068ec6290e82d22) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Chore: remove memory test from CI workflow. This causes issues with Vite 3 dependency resolution, and is no longer necessary for testing our compiler.
+
+## 0.0.5
+
+### Patch Changes
+
+- [#3744](https://github.com/withastro/astro/pull/3744) [`fb9ef401`](https://github.com/withastro/astro/commit/fb9ef4019bd5aec99972cac9bb82f2b6c259292c) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Fix: avoid generating sourcemaps for dev builds of the monorepo - prevents Vite warning logs
+
+## 0.0.4
+
+### Patch Changes
+
+- Updated dependencies [[`4de53ecc`](https://github.com/withastro/astro/commit/4de53eccef346bed843b491b7ab93987d7d85655)]:
+ - @astrojs/webapi@0.12.0
diff --git a/scripts/cmd/build.js b/scripts/cmd/build.js
new file mode 100644
index 000000000..3eb982e10
--- /dev/null
+++ b/scripts/cmd/build.js
@@ -0,0 +1,175 @@
+import fs from 'node:fs/promises';
+import esbuild from 'esbuild';
+import { dim, green, red, yellow } from 'kleur/colors';
+import { glob } from 'tinyglobby';
+import prebuild from './prebuild.js';
+
+/** @type {import('esbuild').BuildOptions} */
+const defaultConfig = {
+ minify: false,
+ format: 'esm',
+ platform: 'node',
+ target: 'node18',
+ sourcemap: false,
+ sourcesContent: false,
+};
+
+const dt = new Intl.DateTimeFormat('en-us', {
+ hour: '2-digit',
+ minute: '2-digit',
+});
+
+function getPrebuilds(isDev, args) {
+ let prebuilds = [];
+ while (args.includes('--prebuild')) {
+ let idx = args.indexOf('--prebuild');
+ prebuilds.push(args[idx + 1]);
+ args.splice(idx, 2);
+ }
+ if (prebuilds.length && isDev) {
+ prebuilds.unshift('--no-minify');
+ }
+ return prebuilds;
+}
+
+export default async function build(...args) {
+ const config = Object.assign({}, defaultConfig);
+ const isDev = args.slice(-1)[0] === 'IS_DEV';
+ const prebuilds = getPrebuilds(isDev, args);
+ const patterns = args
+ .filter((f) => !!f) // remove empty args
+ .filter((f) => !f.startsWith('--')) // remove flags
+ .map((f) => f.replace(/^'/, '').replace(/'$/, '')); // Needed for Windows: glob strings contain surrounding string chars??? remove these
+ let entryPoints = [].concat(
+ ...(await Promise.all(
+ patterns.map((pattern) =>
+ glob(pattern, { filesOnly: true, expandDirectories: false, absolute: true }),
+ ),
+ )),
+ );
+
+ const noClean = args.includes('--no-clean-dist');
+ const bundle = args.includes('--bundle');
+ const forceCJS = args.includes('--force-cjs');
+
+ const { type = 'module', dependencies = {} } = await readPackageJSON('./package.json');
+
+ config.define = {};
+ for (const [key, value] of await getDefinedEntries()) {
+ config.define[`process.env.${key}`] = JSON.stringify(value);
+ }
+ const format = type === 'module' && !forceCJS ? 'esm' : 'cjs';
+
+ const outdir = 'dist';
+
+ if (!noClean) {
+ await clean(outdir);
+ }
+
+ if (!isDev) {
+ await esbuild.build({
+ ...config,
+ bundle,
+ external: bundle ? Object.keys(dependencies) : undefined,
+ entryPoints,
+ outdir,
+ outExtension: forceCJS ? { '.js': '.cjs' } : {},
+ format,
+ });
+ return;
+ }
+
+ const rebuildPlugin = {
+ name: 'astro:rebuild',
+ setup(build) {
+ build.onEnd(async (result) => {
+ if (prebuilds.length) {
+ await prebuild(...prebuilds);
+ }
+ const date = dt.format(new Date());
+ if (result && result.errors.length) {
+ console.error(dim(`[${date}] `) + red(error || result.errors.join('\n')));
+ } else {
+ if (result.warnings.length) {
+ console.info(
+ dim(`[${date}] `) + yellow('! updated with warnings:\n' + result.warnings.join('\n')),
+ );
+ }
+ console.info(dim(`[${date}] `) + green('√ updated'));
+ }
+ });
+ },
+ };
+
+ const builder = await esbuild.context({
+ ...config,
+ entryPoints,
+ outdir,
+ format,
+ sourcemap: 'linked',
+ plugins: [rebuildPlugin],
+ });
+
+ await builder.watch();
+
+ process.on('beforeExit', () => {
+ builder.stop && builder.stop();
+ });
+}
+
+async function clean(outdir) {
+ const files = await glob('**', {
+ cwd: outdir,
+ filesOnly: true,
+ ignore: ['**/*.d.ts'],
+ absolute: true,
+ });
+ await Promise.all(files.map((file) => fs.rm(file, { force: true })));
+}
+
+/**
+ * Contextual `define` values to statically replace in the built JS output.
+ * Available to all packages, but mostly useful for CLIs like `create-astro`.
+ */
+async function getDefinedEntries() {
+ const define = {
+ /** The current version (at the time of building) for the current package, such as `astro` or `@astrojs/sitemap` */
+ PACKAGE_VERSION: await getInternalPackageVersion('./package.json'),
+ /** The current version (at the time of building) for `astro` */
+ ASTRO_VERSION: await getInternalPackageVersion(
+ new URL('../../packages/astro/package.json', import.meta.url),
+ ),
+ /** The current version (at the time of building) for `@astrojs/check` */
+ ASTRO_CHECK_VERSION: await getWorkspacePackageVersion('@astrojs/check'),
+ /** The current version (at the time of building) for `typescript` */
+ TYPESCRIPT_VERSION: await getWorkspacePackageVersion('typescript'),
+ };
+ for (const [key, value] of Object.entries(define)) {
+ if (value === undefined) {
+ delete define[key];
+ }
+ }
+ return Object.entries(define);
+}
+
+async function readPackageJSON(path) {
+ return await fs.readFile(path, { encoding: 'utf8' }).then((res) => JSON.parse(res));
+}
+
+async function getInternalPackageVersion(path) {
+ return readPackageJSON(path).then((res) => res.version);
+}
+
+async function getWorkspacePackageVersion(packageName) {
+ const { dependencies, devDependencies } = await readPackageJSON(
+ new URL('../../package.json', import.meta.url),
+ );
+ const deps = { ...dependencies, ...devDependencies };
+ const version = deps[packageName];
+ if (!version) {
+ throw new Error(
+ `Unable to resolve "${packageName}". Is it a dependency of the workspace root?`,
+ );
+ }
+ return version.replace(/^\D+/, '');
+}
diff --git a/scripts/cmd/prebuild.js b/scripts/cmd/prebuild.js
new file mode 100644
index 000000000..68a3a4f91
--- /dev/null
+++ b/scripts/cmd/prebuild.js
@@ -0,0 +1,117 @@
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath, pathToFileURL } from 'node:url';
+import esbuild from 'esbuild';
+import { red } from 'kleur/colors';
+import { glob } from 'tinyglobby';
+
+function escapeTemplateLiterals(str) {
+ return str.replace(/\`/g, '\\`').replace(/\$\{/g, '\\${');
+}
+
+export default async function prebuild(...args) {
+ let buildToString = args.indexOf('--to-string');
+ if (buildToString !== -1) {
+ args.splice(buildToString, 1);
+ buildToString = true;
+ }
+ let minify = true;
+ let minifyIdx = args.indexOf('--no-minify');
+ if (minifyIdx !== -1) {
+ minify = false;
+ args.splice(minifyIdx, 1);
+ }
+
+ let patterns = args;
+ // NOTE: absolute paths returned are forward slashes on windows
+ let entryPoints = [].concat(
+ ...(await Promise.all(
+ patterns.map((pattern) => glob(pattern, { onlyFiles: true, absolute: true })),
+ )),
+ );
+
+ function getPrebuildURL(entryfilepath, dev = false) {
+ const entryURL = pathToFileURL(entryfilepath);
+ const basename = path.basename(entryfilepath);
+ const ext = path.extname(entryfilepath);
+ const name = basename.slice(0, basename.indexOf(ext));
+ const outname = dev ? `${name}.prebuilt-dev${ext}` : `${name}.prebuilt${ext}`;
+ const outURL = new URL('./' + outname, entryURL);
+ return outURL;
+ }
+
+ async function prebuildFile(filepath) {
+ let tscode = await fs.promises.readFile(filepath, 'utf-8');
+ // If we're bundling a client directive, modify the code to match `packages/astro/src/core/client-directive/build.ts`.
+ // If updating this code, make sure to also update that file.
+ if (filepath.includes('runtime/client')) {
+ // `export default xxxDirective` is a convention used in the current client directives that we use
+ // to make sure we bundle this right. We'll error below if this convention isn't followed.
+ const newTscode = tscode.replace(
+ /export default (.*?)Directive/,
+ (_, name) =>
+ `(self.Astro || (self.Astro = {})).${name} = ${name}Directive;window.dispatchEvent(new Event('astro:${name}'))`,
+ );
+ if (newTscode === tscode) {
+ console.error(
+ red(
+ `${filepath} doesn't follow the \`export default xxxDirective\` convention. The prebuilt output may be wrong. ` +
+ `For more information, check out ${fileURLToPath(import.meta.url)}`,
+ ),
+ );
+ }
+ tscode = newTscode;
+ }
+
+ const esbuildOptions = {
+ stdin: {
+ contents: tscode,
+ resolveDir: path.dirname(filepath),
+ loader: 'ts',
+ sourcefile: filepath,
+ },
+ format: 'iife',
+ target: ['es2018'],
+ minify,
+ bundle: true,
+ write: false,
+ };
+
+ const results = await Promise.all(
+ [
+ {
+ build: await esbuild.build(esbuildOptions),
+ dev: false,
+ },
+ filepath.includes('astro-island')
+ ? {
+ build: await esbuild.build({
+ ...esbuildOptions,
+ define: { 'process.env.NODE_ENV': '"development"' },
+ }),
+ dev: true,
+ }
+ : undefined,
+ ].filter((entry) => entry),
+ );
+
+ for (const result of results) {
+ const code = result.build.outputFiles[0].text.trim();
+ const rootURL = new URL('../../', import.meta.url);
+ const rel = path.relative(fileURLToPath(rootURL), filepath);
+ const generatedCode = escapeTemplateLiterals(code);
+ const mod = `/**
+ * This file is prebuilt from ${rel}
+ * Do not edit this directly, but instead edit that file and rerun the prebuild
+ * to generate this file.
+ */
+
+export default \`${generatedCode}\`;`;
+ const url = getPrebuildURL(filepath, result.dev);
+ await fs.promises.writeFile(url, mod, 'utf-8');
+ }
+ }
+ for (const entrypoint of entryPoints) {
+ await prebuildFile(entrypoint);
+ }
+}
diff --git a/scripts/cmd/test.js b/scripts/cmd/test.js
new file mode 100644
index 000000000..ed8d0e45e
--- /dev/null
+++ b/scripts/cmd/test.js
@@ -0,0 +1,89 @@
+import fs from 'node:fs/promises';
+import path from 'node:path';
+import { run } from 'node:test';
+import { spec } from 'node:test/reporters';
+import { pathToFileURL } from 'node:url';
+import { parseArgs } from 'node:util';
+import { glob } from 'tinyglobby';
+
+const isCI = !!process.env.CI;
+const defaultTimeout = isCI ? 1400000 : 600000;
+
+export default async function test() {
+ const args = parseArgs({
+ allowPositionals: true,
+ options: {
+ // aka --test-name-pattern: https://nodejs.org/api/test.html#filtering-tests-by-name
+ match: { type: 'string', alias: 'm' },
+ // aka --test-only: https://nodejs.org/api/test.html#only-tests
+ only: { type: 'boolean', alias: 'o' },
+ // aka --test-concurrency: https://nodejs.org/api/test.html#test-runner-execution-model
+ parallel: { type: 'boolean', alias: 'p' },
+ // experimental: https://nodejs.org/api/test.html#watch-mode
+ watch: { type: 'boolean', alias: 'w' },
+ // Test timeout in milliseconds (default: 30000ms)
+ timeout: { type: 'string', alias: 't' },
+ // Test setup file
+ setup: { type: 'string', alias: 's' },
+ // Test teardown file
+ teardown: { type: 'string' },
+ },
+ });
+
+ const pattern = args.positionals[1];
+ if (!pattern) throw new Error('Missing test glob pattern');
+
+ const files = await glob(pattern, {
+ filesOnly: true,
+ absolute: true,
+ ignore: ['**/node_modules/**'],
+ });
+
+ // For some reason, the `only` option does not work and we need to explicitly set the CLI flag instead.
+ // Node.js requires opt-in to run .only tests :(
+ // https://nodejs.org/api/test.html#only-tests
+ if (args.values.only) {
+ process.env.NODE_OPTIONS ??= '';
+ process.env.NODE_OPTIONS += ' --test-only';
+ }
+
+ if (!args.values.parallel) {
+ // If not parallel, we create a temporary file that imports all the test files
+ // so that it all runs in a single process.
+ const tempTestFile = path.resolve('./node_modules/.astro/test.mjs');
+ await fs.mkdir(path.dirname(tempTestFile), { recursive: true });
+ await fs.writeFile(
+ tempTestFile,
+ files.map((f) => `import ${JSON.stringify(pathToFileURL(f).toString())};`).join('\n'),
+ );
+
+ files.length = 0;
+ files.push(tempTestFile);
+ }
+
+ const teardownModule = args.values.teardown
+ ? await import(pathToFileURL(path.resolve(args.values.teardown)).toString())
+ : undefined;
+
+ // https://nodejs.org/api/test.html#runoptions
+ run({
+ files,
+ testNamePatterns: args.values.match,
+ concurrency: args.values.parallel,
+ only: args.values.only,
+ setup: args.values.setup,
+ watch: args.values.watch,
+ timeout: args.values.timeout ? Number(args.values.timeout) : defaultTimeout, // Node.js defaults to Infinity, so set better fallback
+ })
+ .on('test:fail', () => {
+ // For some reason, a test fail using the JS API does not set an exit code of 1,
+ // so we set it here manually
+ process.exitCode = 1;
+ })
+ .on('end', () => {
+ const testPassed = process.exitCode === 0 || process.exitCode === undefined;
+ teardownModule?.default(testPassed);
+ })
+ .pipe(new spec())
+ .pipe(process.stdout);
+}
diff --git a/scripts/deps/update-example-versions.js b/scripts/deps/update-example-versions.js
new file mode 100644
index 000000000..7cbde9a0a
--- /dev/null
+++ b/scripts/deps/update-example-versions.js
@@ -0,0 +1,78 @@
+// @ts-check
+import fs from 'node:fs/promises';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { glob } from 'tinyglobby';
+
+/*
+ This file updates the dependencies' versions in `examples/*` to match the workspace packages' versions.
+ This should be run after `changeset version` so the release PR updates all the versions together.
+*/
+
+const rootUrl = new URL('../..', import.meta.url);
+const rootPackageJson = JSON.parse(await fs.readFile(new URL('./package.json', rootUrl), 'utf-8'));
+
+// get all workspace package name to versions
+/** @type {Map<string, string>} */
+const packageToVersions = new Map();
+
+// Changeset detects workspace packages to publish via `workspaces` in package.json.
+// Although this conflicts with the `pnpm-workspace.yaml` config, it's easier to configure what gets
+// published through this field, so this file also respects this field when updating the versions.
+const workspaceDirs = await glob(rootPackageJson.workspaces, {
+ onlyDirectories: true,
+ cwd: fileURLToPath(rootUrl),
+});
+for (const workspaceDir of workspaceDirs) {
+ const packageJsonPath = path.join(workspaceDir, './package.json');
+ const packageJson = await readAndParsePackageJson(packageJsonPath);
+ if (!packageJson) continue;
+
+ if (packageJson.private === true) continue;
+
+ if (!packageJson.name) {
+ throw new Error(`${packageJsonPath} does not contain a "name" field.`);
+ }
+ if (!packageJson.version) {
+ throw new Error(`${packageJsonPath} does not contain a "version" field.`);
+ }
+
+ packageToVersions.set(packageJson.name, packageJson.version);
+}
+
+// Update all examples' package.json
+const exampleDirs = await glob('examples/*', {
+ onlyDirectories: true,
+ cwd: fileURLToPath(rootUrl),
+});
+for (const exampleDir of exampleDirs) {
+ const packageJsonPath = path.join(exampleDir, './package.json');
+ const packageJson = await readAndParsePackageJson(packageJsonPath);
+ if (!packageJson) continue;
+
+ // Update dependencies
+ for (const depName of Object.keys(packageJson.dependencies ?? [])) {
+ if (packageToVersions.has(depName)) {
+ packageJson.dependencies[depName] = `^${packageToVersions.get(depName)}`;
+ }
+ }
+
+ // Update devDependencies
+ for (const depName of Object.keys(packageJson.devDependencies ?? [])) {
+ if (packageToVersions.has(depName)) {
+ packageJson.devDependencies[depName] = `^${packageToVersions.get(depName)}`;
+ }
+ }
+
+ await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
+}
+
+/**
+ * @param {string} packageJsonPath
+ * @returns {Promise<Record<string, any> | undefined>}
+ */
+async function readAndParsePackageJson(packageJsonPath) {
+ try {
+ return JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
+ } catch {}
+}
diff --git a/scripts/index.js b/scripts/index.js
new file mode 100755
index 000000000..9129bd4f4
--- /dev/null
+++ b/scripts/index.js
@@ -0,0 +1,24 @@
+#!/usr/bin/env node
+export default async function run() {
+ const [cmd, ...args] = process.argv.slice(2);
+ switch (cmd) {
+ case 'dev':
+ case 'build': {
+ const { default: build } = await import('./cmd/build.js');
+ await build(...args, cmd === 'dev' ? 'IS_DEV' : undefined);
+ break;
+ }
+ case 'prebuild': {
+ const { default: prebuild } = await import('./cmd/prebuild.js');
+ await prebuild(...args);
+ break;
+ }
+ case 'test': {
+ const { default: test } = await import('./cmd/test.js');
+ await test(...args);
+ break;
+ }
+ }
+}
+
+run();
diff --git a/scripts/jsconfig.json b/scripts/jsconfig.json
new file mode 100644
index 000000000..5cf3835e8
--- /dev/null
+++ b/scripts/jsconfig.json
@@ -0,0 +1,12 @@
+{
+ "compilerOptions": {
+ "declaration": true,
+ "strict": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "target": "esnext"
+ }
+}
diff --git a/scripts/package.json b/scripts/package.json
new file mode 100644
index 000000000..fe83e2e4a
--- /dev/null
+++ b/scripts/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "astro-scripts",
+ "version": "0.0.14",
+ "private": true,
+ "type": "module",
+ "main": "./index.js",
+ "bin": {
+ "astro-scripts": "./index.js"
+ },
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "kleur": "^4.1.5",
+ "p-limit": "^6.2.0",
+ "tinyglobby": "^0.2.12",
+ "tsconfck": "^3.1.5"
+ }
+}
diff --git a/scripts/smoke/check.js b/scripts/smoke/check.js
new file mode 100644
index 000000000..9f0038468
--- /dev/null
+++ b/scripts/smoke/check.js
@@ -0,0 +1,97 @@
+// @ts-check
+
+import { spawn } from 'node:child_process';
+import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
+import * as path from 'node:path';
+import pLimit from 'p-limit';
+import { toJson } from 'tsconfck';
+
+const skippedExamples = ['toolbar-app', 'component', 'server-islands'];
+
+function checkExamples() {
+ let examples = readdirSync('./examples', { withFileTypes: true });
+ examples = examples.filter((dirent) => dirent.isDirectory()).filter((dirent) => !skippedExamples.includes(dirent.name));
+
+ console.log(`Running astro check on ${examples.length} examples...`);
+
+ // Run astro check in parallel with 5 at most
+ const checkPromises = [];
+ const limit = pLimit(5);
+
+ for (const example of examples) {
+ checkPromises.push(
+ limit(
+ () =>
+ new Promise((resolve) => {
+ // Sometimes some examples may get deleted, but after a `git pull` the directory still exists.
+ // This can stall the process time as it'll typecheck the entire monorepo, so do a quick exist
+ // check here before typechecking this directory.
+ if (!existsSync(path.join('./examples/', example.name, 'package.json'))) {
+ resolve(0);
+ return;
+ }
+
+ const originalConfig = prepareExample(example.name);
+ let data = '';
+ const child = spawn('node', ['../../packages/astro/astro.js', 'check'], {
+ cwd: path.join('./examples', example.name),
+ env: { ...process.env, FORCE_COLOR: 'true' },
+ });
+
+ child.stdout.on('data', function (buffer) {
+ data += buffer.toString();
+ });
+
+ child.on('exit', (code) => {
+ if (code !== 0) {
+ console.error(data);
+ }
+ if (originalConfig) {
+ resetExample(example.name, originalConfig);
+ }
+ resolve(code);
+ });
+ })
+ )
+ );
+ }
+
+ Promise.all(checkPromises).then((codes) => {
+ if (codes.some((code) => code !== 0)) {
+ process.exit(1);
+ }
+
+ console.log('No errors found!');
+ });
+}
+
+/**
+ * @param {string} examplePath
+ */
+function prepareExample(examplePath) {
+ const tsconfigPath = path.join('./examples/', examplePath, 'tsconfig.json');
+ if (!existsSync(tsconfigPath)) return
+
+ const originalConfig = readFileSync(tsconfigPath, 'utf-8');
+ const tsconfig = JSON.parse(toJson(originalConfig));
+
+ // Swap to strictest config to make sure it also passes
+ tsconfig.extends = 'astro/tsconfigs/strictest';
+ tsconfig.compilerOptions ??= {}
+ tsconfig.compilerOptions.types = tsconfig.compilerOptions.types ?? []; // Speeds up tests
+
+ writeFileSync(tsconfigPath, JSON.stringify(tsconfig));
+
+ return originalConfig;
+}
+
+/**
+ * @param {string} examplePath
+ * @param {string} originalConfig
+ */
+function resetExample(examplePath, originalConfig) {
+ const tsconfigPath = path.join('./examples/', examplePath, 'tsconfig.json');
+ writeFileSync(tsconfigPath, originalConfig);
+}
+
+checkExamples();