diff --git a/2nd-gen/packages/.ai/skills/swc-consumer-migration/SKILL.md b/2nd-gen/packages/.ai/skills/swc-consumer-migration/SKILL.md new file mode 100644 index 00000000000..cde9c0b7e59 --- /dev/null +++ b/2nd-gen/packages/.ai/skills/swc-consumer-migration/SKILL.md @@ -0,0 +1,123 @@ +--- +name: swc-consumer-migration +description: Use when a consumer of Spectrum Web Components wants to migrate their application from Spectrum 1 (1st-gen, @spectrum-web-components/*) to Spectrum 2 (2nd-gen, @adobe/spectrum-wc). The skill collects per-component consumer migration guides shipped inside node_modules, detects which components the consumer uses from their package.json, and applies each migration component-by-component with explicit checkpoints. Triggers include "migrate spectrum web components", "migrate swc to 2nd gen", "upgrade @spectrum-web-components", "swc 2nd gen migration", "migrate from spectrum 1 to spectrum 2". +alwaysApply: false +--- + +# Spectrum Web Components consumer migration + +Help an application developer migrate their codebase from 1st-gen Spectrum Web Components (`@spectrum-web-components/*`) to 2nd-gen (`@adobe/spectrum-wc`) by reading the per-component consumer migration guides that ship inside their installed `node_modules` and applying each guide to the consumer's source. + +This skill is intended to run inside the **consumer's project**, not the spectrum-web-components repo. It is invoked from Claude Code, Cursor, or Codex. + +## When to use + +- Consumer's `package.json` lists one or more `@spectrum-web-components/*` dependencies and they want to move to `@adobe/spectrum-wc`. +- Consumer asks for a guided, component-by-component migration with checkpoints. +- Do NOT use this skill to author migration guides — that is `.ai/skills/consumer-migration-guide` in the spectrum-web-components repo. + +## Preflight + +Before any other step, confirm: + +1. Current working directory is the **consumer project root** (contains a `package.json`). +2. `node_modules/` exists. If not, ask the user to run their install command (`npm install`, `yarn`, or `pnpm install`) and stop. +3. At least one of `node_modules/@spectrum-web-components/` or `node_modules/@adobe/spectrum-wc/` exists. If neither exists, stop and tell the user nothing to migrate. + +If any check fails, stop and surface the issue. Do not proceed. + +## Step 1: Collect guides + +Run the bundled collection script. The script is dependency-free Node and works from any consumer project root: + +```bash +node /scripts/collect-guides.mjs +``` + +`` is wherever this skill's files live in the consumer project. The script resolves all paths relative to `process.cwd()` so it works whether the skill files were copied into the consumer project or symlinked. + +The script: + +- Walks the consumer's `node_modules/` (including hoisted and nested workspace layouts). +- Finds every `@spectrum-web-components/*` package and the `@adobe/spectrum-wc` package. +- Locates each consumer migration guide by checking these candidate paths inside each package, in order, first match wins: + 1. `/consumer-migration-guide.mdx` + 2. `/dist/consumer-migration-guide.mdx` + 3. `/components//consumer-migration-guide.mdx` + 4. `/dist/components//consumer-migration-guide.mdx` + 5. `/docs/consumer-migration-guide.md` + 6. `/CONSUMER-MIGRATION.md` +- Copies every found guide to `/.swc-migration/guides/.md`. +- Writes `/.swc-migration/manifest.json` listing every package, with `guidePath: null` for packages that ship no guide. + +If a guide is missing from `node_modules/` because the published package does not yet ship it, the user can re-run with a local checkout fallback: + +```bash +node /scripts/collect-guides.mjs --repo=/path/to/spectrum-web-components +``` + +After the script finishes, read `manifest.json` and report a summary to the user: how many packages were detected, how many guides were collected, and which packages have no guide. Do not proceed if zero guides were collected. + +## Step 2: Detect used components + +Read the consumer's `package.json` from the project root. If the project is a workspace (`workspaces` field present), also read each workspace package's `package.json`. + +From every dependency map (`dependencies`, `devDependencies`, `peerDependencies`, `optionalDependencies`), collect: + +- Every entry whose name starts with `@spectrum-web-components/`. +- Whether `@adobe/spectrum-wc` is already present (and at what version). + +Cross-reference with the manifest from Step 1. Build a list: + +| Component | 1st-gen pkg | Installed version | Guide available | +| --------- | ------------------------------ | ----------------- | --------------- | +| badge | @spectrum-web-components/badge | 1.11.2 | yes | +| ... | ... | ... | ... | + +Show this list to the user. **Stop and ask for confirmation** before making any code changes. Confirm: + +- Which components to migrate (default: all that have a guide). +- Whether to migrate in one pass or interactively per component. +- Which directories the agent is allowed to edit (default: `src/`). + +## Step 3: Per-component migration + +For each confirmed component, in dependency-safe order (alphabetical is fine for first pass): + +1. Read `/.swc-migration/guides/.md`. +2. Parse the guide for: package changes, import changes, tag-name changes, prop / attribute changes, removed APIs, codemod hints. The expected guide structure is documented in `references/template.md`. +3. Search the consumer's allowed source directories for usages: + - Imports of `@spectrum-web-components/` and its sub-paths. + - The 1st-gen tag name (e.g. ``) in `.html`, `.htm`, `.tsx`, `.jsx`, `.ts`, `.js`, `.lit`, `.svelte`, `.vue`, `.mdx` files. + - The 1st-gen JS/TS class name if present in the guide. +4. Apply the guide's transformations using the consumer's editor tools. Prefer minimal edits. +5. After each component, surface a diff-style summary to the user: files touched, count of replacements, anything skipped or flagged for manual review. **Stop and ask** before continuing to the next component unless the user explicitly chose batch mode in Step 2. + +After all components are migrated, run the post-migration checklist in `references/usage.md` and report results. + +## Stop conditions + +The skill MUST stop and ask the user explicitly before any of the following: + +- Deleting any file. +- Modifying `package.json` dependency versions, adding `@adobe/spectrum-wc`, or removing `@spectrum-web-components/*` entries. +- Modifying lockfiles (`package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`). +- Running `npm`, `yarn`, or `pnpm` install / add / remove commands. +- Touching files outside the consumer's confirmed source directories. +- Migrating a component for which `manifest.json` reports `guidePath: null`. + +The script and skill never auto-commit. They never push. They never bump versions silently. + +## Failure modes + +- **Guide missing for a detected package.** Skip that component, log it in the final report under "Manual migration required", and continue with the rest. +- **Workspace setup with multiple `package.json` files.** Aggregate dependency lists across workspaces. Show the per-workspace breakdown in Step 2 and confirm scope before editing. +- **Multiple installed versions of the same package** (deduplication failed in `node_modules`). Surface every version found in the manifest. Migrate against the highest version's guide and warn the user about the duplicates — they likely need to dedupe before publishing. +- **Guide refers to a tag/component the consumer never used.** Skip the unused parts silently. Only report changes the consumer actually has source for. +- **Consumer source uses a 1st-gen API that the guide marks as removed with no replacement.** Do not silently delete code. Insert a `TODO(swc-migration):` comment near the usage, list it in the final report under "Removed APIs requiring redesign", and move on. + +## References + +- `references/usage.md` — what each section of a guide means, a worked example, and the post-migration checklist. +- `references/template.md` — canonical guide structure that the skill knows how to parse. Component authors copy this when authoring a new guide. +- `scripts/collect-guides.mjs` — the collection script. Run with `--help` for options. diff --git a/2nd-gen/packages/.ai/skills/swc-consumer-migration/references/template.md b/2nd-gen/packages/.ai/skills/swc-consumer-migration/references/template.md new file mode 100644 index 00000000000..6275e5227d7 --- /dev/null +++ b/2nd-gen/packages/.ai/skills/swc-consumer-migration/references/template.md @@ -0,0 +1,95 @@ +# Consumer migration guide: canonical template + +Component authors copy this template when authoring a new `consumer-migration-guide.mdx` for a 2nd-gen component. The headings below are the structure that `swc-consumer-migration` knows how to parse. Keep them stable. Sentence case. + +The authoritative location of a guide in source is: + +``` +2nd-gen/packages/swc/components//consumer-migration-guide.mdx +``` + +Use `.mdx` so it renders in Storybook. The skill also accepts `.md` as a fallback. + +--- + +````mdx +import { Meta } from '@storybook/addon-docs/blocks'; + + + +# consumer migration guide + +One paragraph: what changed at a glance, and the smallest mental model the consumer needs to migrate. + +## What changed + +### Renamed + +| Area | Spectrum 1 (`sp-`) | Spectrum 2 (`swc-`) | +| ---------------- | -------------------------------------------------- | -------------------------------- | +| Tag | `sp-` | `swc-` | +| Import path | `@spectrum-web-components/` | `@adobe/spectrum-wc/` | +| CSS custom props | `--mod--*` / `--spectrum--*` | `--swc--*` | + +### Added in Spectrum 2 + +| Addition | Notes | +| ---------------------------- | ---------------- | +| (new attribute / slot / API) | (when to use it) | + +### Removed in Spectrum 2 + +| Removed | Replacement | +| ------------- | ------------------------------------------------ | +| (removed API) | (1:1 replacement, or "no replacement — see ...") | + +## Update your code + +### 1. Update the import + +```js +// Before +import '@spectrum-web-components//sp-.js'; +// After +import '@adobe/spectrum-wc/'; +``` +```` + +### 2. Rename the tag + +```html + + ...>...> + + ...>...> +``` + +### 3. + +(One subsection per concrete code change consumers must make. Each subsection MUST contain a Before/After fenced code block so the skill can extract the transformation.) + +## Styling + +(Custom-property renames. Note any properties that have no 1:1 replacement.) + +## Accessibility + +(Consumer-facing accessibility deltas: required attribute additions, ARIA changes, focus behavior changes.) + +## Verification + +(Concrete checks the consumer should run after migrating: visual smoke test path, automated test command, screen reader behaviors to verify.) + +``` + +--- + +## Authoring rules + +- **Sentence case** for every heading. +- **Tables** are the source of truth for renames. Code blocks under `## Update your code` are the source of truth for concrete edits. +- **Before / After** must appear inside fenced code blocks for every transformation. The skill keys off this. +- **No links to maintainer docs.** Link only to public consumer docs (Storybook, npm). +- **Do not include `Components/` in the ``** — `titlePrefix` adds it automatically. +- If a section does not apply (e.g. no styling changes), keep the heading and write "No changes." underneath. Do not delete the heading. +``` diff --git a/2nd-gen/packages/.ai/skills/swc-consumer-migration/references/usage.md b/2nd-gen/packages/.ai/skills/swc-consumer-migration/references/usage.md new file mode 100644 index 00000000000..705a4719419 --- /dev/null +++ b/2nd-gen/packages/.ai/skills/swc-consumer-migration/references/usage.md @@ -0,0 +1,106 @@ +# Usage reference: parsing guides and applying migrations + +This reference is loaded on demand by `swc-consumer-migration`. It explains how to interpret a consumer migration guide, walks through one worked example, and lists the post-migration checklist. + +## How guides are structured + +Every guide ships as a single `.mdx` (or `.md`) file. The skill expects this shape, with sentence-case headings: + +``` +# consumer migration guide + +(One-paragraph summary: what changed at a glance.) + +## What changed + +### Renamed +(Markdown table: Area | Spectrum 1 | Spectrum 2) + +### Added in Spectrum 2 +(Markdown table: Addition | Notes) + +### Removed in Spectrum 2 +(Markdown table: Removed | Replacement) + +## Update your code + +### 1. Update the import +### 2. Rename the tag +### 3. + +## Styling +(custom-property renames, semantic vs. non-semantic notes) + +## Accessibility +(consumer-facing a11y deltas) + +## Verification +(what to test after migrating) +``` + +When parsing, extract: + +| From section | What the skill should pull out | +| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------ | +| `## What changed > Renamed` | Tag-name renames, package / import-path renames, custom-property renames | +| `## What changed > Added in Spectrum 2` | New attributes/props/slots — informational only, no automatic edits | +| `## What changed > Removed in Spectrum 2` | Removed APIs that need a replacement decision; flag with `TODO(swc-migration):` if no 1:1 replacement exists | +| `## Update your code` | Concrete code transformations the skill should apply | +| `## Styling` | Custom-property renames the skill should apply across CSS and inline `style` strings | +| `## Accessibility` | Required attribute additions (e.g. `aria-label` for icon-only) | +| `## Verification` | Checks to run after migration | + +Tables are the source of truth for renames. Code blocks under `## Update your code` are the source of truth for concrete transformations. Treat prose as commentary unless it contradicts the tables. + +If a guide deviates from this structure (older guides, partial guides), parse defensively: search for the keywords "Before" / "After" inside fenced code blocks and fall back to surfacing the diff to the user for manual review. + +## Worked example: badge + +Source guide (shipped today): `2nd-gen/packages/swc/components/badge/consumer-migration-guide.mdx`. + +After Step 1 collects guides, the skill reads `/.swc-migration/guides/badge.md` and extracts: + +- **Package rename:** `@spectrum-web-components/badge` → `@adobe/spectrum-wc/badge` +- **Import rename:** `@spectrum-web-components/badge/sp-badge.js` → `@adobe/spectrum-wc/badge` +- **Tag rename:** `` → `` +- **Custom-property rename:** `--mod-badge-*` and `--spectrum-badge-*` → `--swc-badge-*` (semantic variants only) +- **Removed exports:** `BADGE_VARIANTS`, `FIXED_VALUES`, `BadgeVariant`, `FixedValues` → use `Badge.VARIANTS`, `Badge.FIXED_VALUES`, or `typeof Badge.prototype.variant` +- **Accessibility delta:** add `aria-label` to icon-only badges; add `aria-hidden="true"` to decorative icons paired with text + +Concrete edits the skill applies in the consumer's `src/`: + +1. **Imports.** For every file that imports from `@spectrum-web-components/badge` (any sub-path), rewrite to the corresponding `@adobe/spectrum-wc/badge` import. If the original import had a sub-path (e.g. `/sp-badge.js`), drop the sub-path — the new package exposes the side-effectful registration via the package root. +2. **JSX/HTML tags.** Replace `` with ``. Preserve attributes and children. Apply across `.html`, `.htm`, `.tsx`, `.jsx`, `.ts`, `.js`, `.lit`, `.svelte`, `.vue`, `.mdx`. +3. **Named imports.** Replace named imports of `BADGE_VARIANTS` / `FIXED_VALUES` / `BadgeVariant` / `FixedValues` with `Badge.VARIANTS` / `Badge.FIXED_VALUES` / `typeof Badge.prototype.variant`. Pull `Badge` into scope if not already imported. +4. **Custom properties.** In CSS files, SCSS files, and inline `style=` strings, replace `--mod-badge-` and `--spectrum-badge-` with `--swc-badge-` only when the surrounding declaration applies to a semantic variant (positive / informative / negative / notice / neutral). Non-semantic per-color overrides have no replacement — leave them and flag with `TODO(swc-migration):` referencing the "Removed" table. +5. **Forced-colors.** `--highcontrast-badge-border-color` has no replacement. Flag every usage with `TODO(swc-migration): no forced-colors hook in Spectrum 2 badge` and leave the existing rule in place. +6. **Accessibility.** For every `` whose only children are icons (no text node), add `aria-label="..."` if missing and surface a TODO for the user to fill in the label. For badges with both icon and text, add `aria-hidden="true"` to the icon element. + +After applying edits, surface a per-file diff summary and stop for confirmation before moving on to the next component. + +## Post-migration checklist + +Run these after every component is migrated: + +- [ ] **Imports resolve.** No file in the consumer's source still imports from `@spectrum-web-components/`. +- [ ] **Tag references updated.** Search for the old tag name across all source files. Zero hits expected. +- [ ] **Type references updated.** TypeScript compiles. No `any` introduced where a 1st-gen named export used to be. +- [ ] **Custom properties.** Search for `--mod--` and `--spectrum--`. Either renamed to `--swc--` or flagged with `TODO(swc-migration):`. +- [ ] **Removed APIs.** Every removed API the consumer was using is either replaced or flagged with a `TODO(swc-migration):` comment. Nothing silently deleted. +- [ ] **Accessibility deltas applied.** For every component with an a11y delta in its guide, the corresponding markup change was applied or flagged. +- [ ] **Tests reference updated tags.** Test files (`*.test.*`, `*.spec.*`, `*.stories.*`) updated to use the new tag names and imports. +- [ ] **Lint and type check pass.** Run the consumer's existing scripts (do not invent new ones). If they fail, surface the failures verbatim and stop. + +## What the skill does NOT do + +- Bump dependency versions in `package.json` — the user runs their own install. +- Run install commands or modify lockfiles. +- Rewrite tests beyond import/tag substitution. +- Apply visual or design changes beyond what the guide explicitly specifies. +- Migrate components for which no guide was collected. + +If any of those are needed, surface a clear note in the final report and let the user act. + +## Re-running + +Re-running `collect-guides.mjs` is safe and idempotent. It overwrites the previous guide copies and the manifest. Source files in the consumer project are never touched by the script — only by the agent applying migrations under explicit confirmation. diff --git a/2nd-gen/packages/.ai/skills/swc-consumer-migration/scripts/collect-guides.mjs b/2nd-gen/packages/.ai/skills/swc-consumer-migration/scripts/collect-guides.mjs new file mode 100644 index 00000000000..1f40ab73c24 --- /dev/null +++ b/2nd-gen/packages/.ai/skills/swc-consumer-migration/scripts/collect-guides.mjs @@ -0,0 +1,392 @@ +#!/usr/bin/env node + +/** + * Copyright 2026 Adobe. All rights reserved. + * Licensed under the Apache License, Version 2.0. + * + * collect-guides.mjs + * + * Discovers Spectrum Web Components packages in a consumer project's + * node_modules and copies each component's consumer migration guide into + * /.swc-migration/guides/.md. Writes a manifest.json + * describing every package found (with or without a guide). + * + * No external dependencies. Node >= 18. + * + * Usage: + * node path/to/collect-guides.mjs run from consumer project root + * node path/to/collect-guides.mjs --help print usage + * node path/to/collect-guides.mjs --cwd=

override consumer root + * node path/to/collect-guides.mjs --repo=

fall back to a local checkout + * of spectrum-web-components when + * guides are missing in node_modules + * + * Supported published layouts (checked in order, first match wins): + * 1. /consumer-migration-guide.mdx (per-component pkg, source layout) + * 2. /dist/consumer-migration-guide.mdx (per-component pkg, dist) + * 3. /components//consumer-migration-guide.mdx + * 4. /dist/components//consumer-migration-guide.mdx + * 5. /docs/consumer-migration-guide.md (fallback naming) + * 6. /CONSUMER-MIGRATION.md (legacy fallback) + * + * As of writing, the canonical source-of-truth path inside the + * spectrum-web-components repo is: + * 2nd-gen/packages/swc/components//consumer-migration-guide.mdx + * Published 2nd-gen package (@adobe/spectrum-wc) only ships dist/, so + * guides may not be present in node_modules until shipped. Use --repo to + * point at a local checkout in that case. + */ + +import { + copyFileSync, + existsSync, + mkdirSync, + readdirSync, + readFileSync, + writeFileSync, +} from 'node:fs'; +import { join, resolve } from 'node:path'; + +const HELP = `collect-guides.mjs + +Collect Spectrum Web Components consumer migration guides from a consumer +project's node_modules into /.swc-migration/. + +Options: + --help Show this help and exit. + --cwd= Consumer project root (default: process.cwd()). + --repo= Local checkout of spectrum-web-components to fall back to + when a guide is missing in node_modules. + --quiet Suppress per-package log lines. + +Output: + /.swc-migration/guides/.md one file per guide found + /.swc-migration/manifest.json full inventory + +Exit codes: + 0 completed (even with partial coverage) + 1 node_modules has no Spectrum Web Components packages and no --repo set + 2 invalid arguments +`; + +const SCOPE_NAMES = ['@spectrum-web-components', '@adobe']; +const SECOND_GEN_PKG = '@adobe/spectrum-wc'; + +const GUIDE_BASENAMES = [ + 'consumer-migration-guide.mdx', + 'consumer-migration-guide.md', + 'CONSUMER-MIGRATION.md', +]; + +function parseArgs(argv) { + const out = { cwd: process.cwd(), repo: null, quiet: false, help: false }; + for (const arg of argv.slice(2)) { + if (arg === '--help' || arg === '-h') { + out.help = true; + } else if (arg === '--quiet') { + out.quiet = true; + } else if (arg.startsWith('--cwd=')) { + out.cwd = resolve(arg.slice(6)); + } else if (arg.startsWith('--repo=')) { + out.repo = resolve(arg.slice(7)); + } else { + console.error(`Unknown argument: ${arg}`); + console.error(HELP); + process.exit(2); + } + } + return out; +} + +function safeReaddir(dir) { + try { + return readdirSync(dir, { withFileTypes: true }); + } catch { + return []; + } +} + +function readPkgJson(pkgDir) { + const p = join(pkgDir, 'package.json'); + if (!existsSync(p)) { + return null; + } + try { + return JSON.parse(readFileSync(p, 'utf-8')); + } catch { + return null; + } +} + +/** Find every Spectrum Web Components package under node_modules (hoisted + nested). */ +function findSwcPackages(rootCwd) { + const seen = new Map(); + const queue = [join(rootCwd, 'node_modules')]; + + while (queue.length) { + const nm = queue.shift(); + if (!existsSync(nm)) { + continue; + } + + for (const entry of safeReaddir(nm)) { + if (!entry.isDirectory()) { + continue; + } + + // Scoped package directory. + if (entry.name.startsWith('@')) { + const scopeDir = join(nm, entry.name); + if (!SCOPE_NAMES.includes(entry.name)) { + // Still descend into nested node_modules under non-target scopes. + for (const sub of safeReaddir(scopeDir)) { + if (sub.isDirectory()) { + const inner = join(scopeDir, sub.name, 'node_modules'); + if (existsSync(inner)) { + queue.push(inner); + } + } + } + continue; + } + for (const sub of safeReaddir(scopeDir)) { + if (!sub.isDirectory()) { + continue; + } + const pkgDir = join(scopeDir, sub.name); + const pkgJson = readPkgJson(pkgDir); + if (pkgJson && typeof pkgJson.name === 'string') { + if (!seen.has(pkgJson.name)) { + seen.set(pkgJson.name, { dir: pkgDir, pkgJson }); + } + } + const inner = join(pkgDir, 'node_modules'); + if (existsSync(inner)) { + queue.push(inner); + } + } + continue; + } + + // Unscoped package — descend into nested node_modules. + const pkgDir = join(nm, entry.name); + const inner = join(pkgDir, 'node_modules'); + if (existsSync(inner)) { + queue.push(inner); + } + } + } + + return seen; +} + +function deriveComponentName(pkgName) { + // @spectrum-web-components/badge -> badge + // @adobe/spectrum-wc -> spectrum-wc (handled separately for sub-components) + const slash = pkgName.indexOf('/'); + return slash === -1 ? pkgName : pkgName.slice(slash + 1); +} + +/** Look for a guide file directly inside a package directory. */ +function findGuideInDir(dir) { + for (const name of GUIDE_BASENAMES) { + const direct = join(dir, name); + if (existsSync(direct)) { + return direct; + } + } + for (const sub of ['docs', 'dist']) { + for (const name of GUIDE_BASENAMES) { + const p = join(dir, sub, name); + if (existsSync(p)) { + return p; + } + } + } + return null; +} + +/** Enumerate per-component guides inside the 2nd-gen single-package layout. */ +function findSecondGenComponentGuides(pkgDir) { + const out = []; // { component, sourcePath } + const componentRoots = [ + join(pkgDir, 'components'), + join(pkgDir, 'dist', 'components'), + join(pkgDir, 'src', 'components'), + ]; + for (const root of componentRoots) { + if (!existsSync(root)) { + continue; + } + for (const entry of safeReaddir(root)) { + if (!entry.isDirectory()) { + continue; + } + const compDir = join(root, entry.name); + const guide = findGuideInDir(compDir); + if (guide) { + out.push({ component: entry.name, sourcePath: guide }); + } + } + } + return out; +} + +/** Local-repo fallback: read guides directly from a spectrum-web-components checkout. */ +function findRepoGuides(repoPath) { + const out = []; + const componentsDir = join( + repoPath, + '2nd-gen', + 'packages', + 'swc', + 'components' + ); + if (!existsSync(componentsDir)) { + return out; + } + for (const entry of safeReaddir(componentsDir)) { + if (!entry.isDirectory()) { + continue; + } + const guide = findGuideInDir(join(componentsDir, entry.name)); + if (guide) { + out.push({ component: entry.name, sourcePath: guide }); + } + } + return out; +} + +function ensureDir(dir) { + if (!existsSync(dir)) { + mkdirSync(dir, { recursive: true }); + } +} + +function copyGuide(srcPath, destDir, component) { + ensureDir(destDir); + const destName = `${component}.md`; + const destPath = join(destDir, destName); + copyFileSync(srcPath, destPath); + return destPath; +} + +function main() { + const args = parseArgs(process.argv); + if (args.help) { + process.stdout.write(HELP); + return; + } + + const cwd = args.cwd; + const outDir = join(cwd, '.swc-migration'); + const guidesDir = join(outDir, 'guides'); + ensureDir(guidesDir); + + const log = (msg) => { + if (!args.quiet) { + process.stdout.write(msg + '\n'); + } + }; + + const packages = findSwcPackages(cwd); + const manifest = []; + + if (packages.size === 0 && !args.repo) { + console.error( + 'No @spectrum-web-components or @adobe/spectrum-wc packages found in node_modules.' + ); + console.error( + 'Run install first, or pass --repo=.' + ); + process.exit(1); + } + + // Per-package guides (1st-gen-style: one package per component). + for (const [name, info] of packages) { + if (name === SECOND_GEN_PKG) { + continue; + } // handled below + const guide = findGuideInDir(info.dir); + const component = deriveComponentName(name); + const entry = { + packageName: name, + packageVersion: info.pkgJson.version || null, + sourcePackagePath: info.dir, + component, + sourceGuidePath: guide, + guidePath: null, + }; + if (guide) { + entry.guidePath = copyGuide(guide, guidesDir, component); + log(`+ ${name}@${entry.packageVersion} (${component})`); + } else { + log(`- ${name}@${entry.packageVersion} (no guide found)`); + } + manifest.push(entry); + } + + // 2nd-gen single-package layout: components live inside one package. + const secondGen = packages.get(SECOND_GEN_PKG); + if (secondGen) { + const guides = findSecondGenComponentGuides(secondGen.dir); + for (const g of guides) { + const dest = copyGuide(g.sourcePath, guidesDir, g.component); + manifest.push({ + packageName: SECOND_GEN_PKG, + packageVersion: secondGen.pkgJson.version || null, + sourcePackagePath: secondGen.dir, + component: g.component, + sourceGuidePath: g.sourcePath, + guidePath: dest, + }); + log(`+ ${SECOND_GEN_PKG}/${g.component}`); + } + } + + // Repo fallback — fills in any guide we couldn't get from node_modules. + if (args.repo) { + const have = new Set( + manifest.filter((m) => m.guidePath).map((m) => m.component) + ); + const repoGuides = findRepoGuides(args.repo); + for (const g of repoGuides) { + if (have.has(g.component)) { + continue; + } + const dest = copyGuide(g.sourcePath, guidesDir, g.component); + manifest.push({ + packageName: '(repo fallback)', + packageVersion: null, + sourcePackagePath: args.repo, + component: g.component, + sourceGuidePath: g.sourcePath, + guidePath: dest, + }); + log(`+ (repo) ${g.component}`); + } + } + + const manifestPath = join(outDir, 'manifest.json'); + writeFileSync( + manifestPath, + JSON.stringify( + { + generatedAt: new Date().toISOString(), + cwd, + repoFallback: args.repo, + entries: manifest, + }, + null, + 2 + ) + '\n' + ); + + const withGuide = manifest.filter((m) => m.guidePath).length; + const without = manifest.length - withGuide; + log(`\nCollected ${withGuide} guides. ${without} packages had no guide.`); + log(`Manifest: ${manifestPath}`); + log(`Guides: ${guidesDir}`); +} + +main();