From f2f16a8d8841bf576ae1c4821af479cfe0e4b068 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B2=E1=84=8B=E1=85=AD=E1=86=BC=E1=84=90?= =?UTF-8?q?=E1=85=A2?= Date: Fri, 19 Jun 2026 09:57:14 +0900 Subject: [PATCH] =?UTF-8?q?ci:=20=EB=B3=80=EA=B2=BD=20=EB=B2=94=EC=9C=84?= =?UTF-8?q?=EB=B3=84=20=EA=B2=80=EC=A6=9D=20=EC=B6=95=EC=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 48 ++++++++- scripts/ci-scope.mjs | 150 +++++++++++++++++++++++++++++ scripts/evaluate-extension-lab.mjs | 4 +- 3 files changed, 199 insertions(+), 3 deletions(-) create mode 100644 scripts/ci-scope.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2687c8e0..d14ff567 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,14 +15,34 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + with: + fetch-depth: 0 - uses: actions/setup-node@v6 with: node-version: "22" + - name: Detect changed scope + id: scope + env: + CI_BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }} + CI_HEAD_SHA: ${{ github.sha }} + run: node scripts/ci-scope.mjs - name: Install dependencies + if: steps.scope.outputs.package_any == 'true' run: npm ci --no-audit --no-fund - name: Verify package and official extensions + if: steps.scope.outputs.package_full == 'true' run: npm run typecheck && npm test && npm run build && npm run smoke:package && npm run extensions:verify && npm run docs:evaluate + - name: Verify package documentation + if: steps.scope.outputs.package_full != 'true' && (steps.scope.outputs.package_docs == 'true' || steps.scope.outputs.package_smoke == 'true') + run: | + if [ "${{ steps.scope.outputs.package_docs }}" = "true" ]; then + npm run docs:evaluate + fi + if [ "${{ steps.scope.outputs.package_smoke }}" = "true" ]; then + npm run smoke:package + fi - name: Check package contents + if: steps.scope.outputs.package_full == 'true' || steps.scope.outputs.package_smoke == 'true' env: npm_config_cache: ${{ runner.temp }}/npm-cache run: npm pack -w @interactive-os/json-document --dry-run @@ -37,23 +57,47 @@ jobs: - uses: actions/setup-node@v6 with: node-version: "22" + - name: Detect changed scope + id: scope + env: + CI_BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }} + CI_HEAD_SHA: ${{ github.sha }} + run: node scripts/ci-scope.mjs - name: Install dependencies + if: steps.scope.outputs.lab_extensions == 'true' run: npm ci --no-audit --no-fund - name: Verify changed lab extension dist imports + if: steps.scope.outputs.lab_extensions == 'true' env: LAB_EXTENSIONS_BASE: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }} LAB_EXTENSIONS_HEAD: ${{ github.sha }} - run: npm run build && npm run labs:extensions:verify:changed + run: npm run labs:extensions:verify:changed playground-site: name: Playgrounds and site runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + with: + fetch-depth: 0 - uses: actions/setup-node@v6 with: node-version: "22" + - name: Detect changed scope + id: scope + env: + CI_BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }} + CI_HEAD_SHA: ${{ github.sha }} + run: node scripts/ci-scope.mjs - name: Install dependencies + if: steps.scope.outputs.site == 'true' || steps.scope.outputs.playground == 'true' || steps.scope.outputs.browser == 'true' run: npm ci --no-audit --no-fund - name: Verify playgrounds and site - run: npm run playground:typecheck && npm run playground:test && npm run playground:build && npm run site:evaluate && npm run site:verify:pages && npm run browser:test + if: steps.scope.outputs.playground == 'true' + run: npm run playground:typecheck && npm run playground:test && npm run playground:build + - name: Verify site + if: steps.scope.outputs.site == 'true' + run: npm run site:verify:pages + - name: Verify browser behavior + if: steps.scope.outputs.browser == 'true' + run: npm run browser:test diff --git a/scripts/ci-scope.mjs b/scripts/ci-scope.mjs new file mode 100644 index 00000000..095cee61 --- /dev/null +++ b/scripts/ci-scope.mjs @@ -0,0 +1,150 @@ +import { appendFileSync } from "node:fs"; +import { spawnSync } from "node:child_process"; + +const root = new URL("..", import.meta.url).pathname; +const base = option("--base") ?? process.env.CI_BASE_SHA ?? null; +const head = option("--head") ?? process.env.CI_HEAD_SHA ?? "HEAD"; + +const diff = changedFiles(); +const all = diff.files === null; +const files = diff.files ?? []; + +const rootDependency = [/^package(?:-lock)?\.json$/]; +const ciWorkflow = [/^\.github\/workflows\/ci\.yml$/, /^scripts\/ci-scope\.mjs$/]; +const coreRuntime = [ + /^packages\/json-document\/src\//, + /^packages\/json-document\/dist\//, + /^packages\/json-document\/tests\//, + /^packages\/json-document\/(?:package\.json|public-contract\.json|tsconfig(?:\.test)?\.json|vitest\.config\.ts|eslint\.config\.js)$/, +]; +const coreDependency = [ + /^packages\/json-document\/src\//, + /^packages\/json-document\/dist\//, + /^packages\/json-document\/(?:package\.json|public-contract\.json|tsconfig\.json)$/, +]; +const officialExtensionRuntime = [ + /^packages\/(?!json-document\/)[^/]+\/(?:src|tests)\//, + /^packages\/(?!json-document\/)[^/]+\/(?:package\.json|tsconfig(?:\.test)?\.json|vitest\.config\.ts|eslint\.config\.js)$/, +]; +const packageDocs = [ + /^docs\//, + /^llms\.txt$/, + /^packages\/[^/]+\/README\.md$/, + /^apps\/site\/src\/generated\//, + /^scripts\/(?:generate-docs|evaluate-docs)\.mjs$/, +]; +const packageSmoke = [ + /^packages\/json-document\/README\.md$/, + /^packages\/json-document\/tests\/smoke\//, +]; +const packageTooling = [ + /^scripts\/(?:run-workspace-scripts|evaluate-extensions)\.mjs$/, +]; +const labRuntime = [ + /^labs\/extensions\//, + /^scripts\/evaluate-extension-lab\.mjs$/, +]; +const siteRuntime = [ + /^apps\/site\//, + /^docs\//, + /^llms\.txt$/, + /^packages\/[^/]+\/README\.md$/, + /^scripts\/(?:generate-docs|evaluate-docs|evaluate-site|evaluate-site-http|evaluate-live-site)\.mjs$/, +]; +const browserRuntime = [ + /^tests\/browser\//, + /^playwright\.config\.ts$/, + /^apps\/site\/(?:src\/(?!generated\/)|tests\/)/, + /^apps\/site\/(?:package\.json|vite\.config\.ts|tsconfig(?:\.node)?\.json)$/, +]; +const playgroundRuntime = [ + /^apps\/(?!site\/)[^/]+\//, +]; + +const packageFull = matches([ + ...rootDependency, + ...ciWorkflow, + ...coreRuntime, + ...officialExtensionRuntime, + ...packageTooling, +]); +const packageDocsChanged = matches(packageDocs); +const packageSmokeChanged = matches(packageSmoke); +const packageAny = packageFull || packageDocsChanged || packageSmokeChanged; +const labExtensions = matches([ + ...rootDependency, + ...ciWorkflow, + ...coreDependency, + ...labRuntime, +]); +const site = matches([ + ...ciWorkflow, + ...siteRuntime, +]); +const playground = matches([ + ...ciWorkflow, + ...coreRuntime, + ...officialExtensionRuntime, + ...playgroundRuntime, +]); +const browser = matches([ + ...ciWorkflow, + ...coreRuntime, + ...officialExtensionRuntime, + ...browserRuntime, +]); + +const outputs = { + package_full: packageFull, + package_docs: packageDocsChanged, + package_smoke: packageSmokeChanged, + package_any: packageAny, + lab_extensions: labExtensions, + site, + playground, + browser, +}; + +console.log(`ci scope: ${diff.reason}`); +if (files.length > 0) { + for (const file of files) console.log(`- ${file}`); +} +for (const [name, value] of Object.entries(outputs)) { + console.log(`${name}=${value}`); +} + +if (process.env.GITHUB_OUTPUT) { + const content = Object.entries(outputs) + .map(([name, value]) => `${name}=${value ? "true" : "false"}`) + .join("\n"); + appendFileSync(process.env.GITHUB_OUTPUT, `${content}\n`); +} + +function option(name) { + const index = process.argv.indexOf(name); + if (index === -1) return null; + return process.argv[index + 1] ?? null; +} + +function changedFiles() { + if (base === null || /^0+$/.test(base)) { + return { files: null, reason: "missing diff base; running all scopes" }; + } + + const result = spawnSync("git", ["diff", "--name-only", base, head], { + cwd: root, + encoding: "utf8", + }); + if (result.status !== 0) { + return { files: null, reason: `git diff failed for ${base}..${head}; running all scopes` }; + } + + return { + files: result.stdout.split(/\r?\n/).filter((path) => path.length > 0), + reason: `${base}..${head}`, + }; +} + +function matches(patterns) { + return all || files.some((file) => patterns.some((pattern) => pattern.test(file))); +} diff --git a/scripts/evaluate-extension-lab.mjs b/scripts/evaluate-extension-lab.mjs index 98331245..d44034d6 100644 --- a/scripts/evaluate-extension-lab.mjs +++ b/scripts/evaluate-extension-lab.mjs @@ -10,7 +10,9 @@ const verify = process.argv.includes("--verify"); const verifyChanged = process.argv.includes("--changed"); const fullVerificationPathPatterns = [ /^package(?:-lock)?\.json$/, - /^packages\/json-document\//, + /^packages\/json-document\/src\//, + /^packages\/json-document\/dist\//, + /^packages\/json-document\/(?:package\.json|public-contract\.json|tsconfig\.json)$/, /^scripts\/evaluate-extension-lab\.mjs$/, ]; const retiredLabNames = new Set([