diff --git a/.github/workflows/publish-canary-releases.yml b/.github/workflows/publish-canary-releases.yml deleted file mode 100644 index 97a74709..00000000 --- a/.github/workflows/publish-canary-releases.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Publish Canary Releases - -on: - workflow_dispatch: - branches: - - main - push: - branches: - - main - -env: - # See https://consoledonottrack.com/ - DO_NOT_TRACK: '1' - -jobs: - build-and-publish-snapshots-to-npm: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Install Dependencies - uses: ./.github/workflows/actions/install-dependencies - - - name: Run Test & Lint - run: pnpm test - - - name: Run Build Step - run: pnpm build - - - name: Configure NPM token - run: | - pnpm config set '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}" - env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Publish Canary Release - run: | - pnpm pkg delete devDependencies - pnpm changeset version --snapshot canary - pnpm build - pnpm publish-packages - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - PUBLISH_TAG: canary diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml index 272bb3e8..1171727f 100644 --- a/.github/workflows/publish-packages.yml +++ b/.github/workflows/publish-packages.yml @@ -1,19 +1,17 @@ -name: Version & Publish Packages +name: Version Packages on: - workflow_dispatch: - branches: - - main push: branches: - main -env: - # See https://consoledonottrack.com/ - DO_NOT_TRACK: '1' +permissions: + contents: write + pull-requests: write jobs: - build-and-publish-to-npm: + version: + name: Create Version PR runs-on: ubuntu-latest steps: - name: Checkout @@ -22,29 +20,10 @@ jobs: - name: Install Dependencies uses: ./.github/workflows/actions/install-dependencies - - name: Run Build Step - run: pnpm build - - - name: Configure NPM token - run: | - pnpm config set '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}" - env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Create Changesets Pull Request or Trigger an NPM Publish - id: changesets + - name: Create Changesets Pull Request uses: changesets/action@v1 + with: + title: 'chore: version packages' + commit: 'chore: version packages' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Choose Build Step - id: build-step-decider - run: - echo "step-name=${{ steps.changesets.outputs.hasChangesets == 'false' && 'publish-packages' || 'build' }}" >> - $GITHUB_OUTPUT - - - name: Run Build Step - run: pnpm ${{ steps.build-step-decider.outputs.step-name }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index 8cafefa7..e43ba5a0 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -1,44 +1,18 @@ -name: Pull requests +name: Pull Requests on: pull_request: -env: - # Among other things, opts out of Turborepo telemetry - # See https://consoledonottrack.com/ - DO_NOT_TRACK: '1' - # Some tasks slow down considerably on GitHub Actions runners when concurrency is high - TURBO_CONCURRENCY: 1 - jobs: - # Needed for grouping check-web3 strategies into one check for mergify + typescript-build: + uses: ./.github/workflows/typescript-build.yml + + typescript-test: + uses: ./.github/workflows/typescript-test.yml + + # Needed for grouping strategies into one check for mergify all-pr-checks: runs-on: ubuntu-latest - needs: build-and-test + needs: [typescript-build, typescript-test] steps: - run: echo "Done" - - build-and-test: - runs-on: ubuntu-latest - - strategy: - matrix: - node: - - 'current' - - 'lts/*' - - name: Build & Test on Node ${{ matrix.node }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Install Dependencies - uses: ./.github/workflows/actions/install-dependencies - with: - version: ${{ matrix.node }} - - - name: Build - run: pnpm build - - - name: Test & Lint - run: pnpm test diff --git a/.github/workflows/typescript-build.yml b/.github/workflows/typescript-build.yml new file mode 100644 index 00000000..22cf3622 --- /dev/null +++ b/.github/workflows/typescript-build.yml @@ -0,0 +1,24 @@ +name: TypeScript Build + +on: + workflow_call: + +env: + DO_NOT_TRACK: '1' + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Dependencies + uses: ./.github/workflows/actions/install-dependencies + + - name: Build + run: pnpm build + + - name: Type check + run: pnpm test:types diff --git a/.github/workflows/typescript-publish.yml b/.github/workflows/typescript-publish.yml new file mode 100644 index 00000000..1aec8561 --- /dev/null +++ b/.github/workflows/typescript-publish.yml @@ -0,0 +1,259 @@ +name: Publish TypeScript Packages + +on: + push: + branches: + - main + workflow_dispatch: + inputs: + publish-to-npm: + description: 'Publish to npm registry' + required: true + default: true + type: boolean + create-github-release: + description: 'Create GitHub release' + required: true + default: true + type: boolean + +permissions: + contents: write # Push tags, create releases + id-token: write # npm provenance (OIDC) + pull-requests: write # Comment on PRs + +# Prevent concurrent publishes +concurrency: + group: typescript-publish + cancel-in-progress: false + +jobs: + typescript-build: + uses: ./.github/workflows/typescript-build.yml + + typescript-test: + uses: ./.github/workflows/typescript-test.yml + + publish: + name: Publish create-solana-dapp + runs-on: ubuntu-latest + needs: [typescript-build, typescript-test] + env: + NPM_CONFIG_PROVENANCE: true + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Git user + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Install Dependencies + uses: ./.github/workflows/actions/install-dependencies + + - name: Setup npm registry auth + uses: actions/setup-node@v4 + with: + registry-url: 'https://registry.npmjs.org' + + - name: Determine publish type + id: publish-type + run: | + if [ "${{ github.event_name }}" == "push" ]; then + echo "type=canary" >> $GITHUB_OUTPUT + echo "Publish type: canary (push to main)" + else + echo "type=release" >> $GITHUB_OUTPUT + echo "Publish type: release (manual dispatch)" + fi + + # --- Canary: snapshot version via changesets --- + - name: Version as canary snapshot + if: steps.publish-type.outputs.type == 'canary' + run: pnpm changeset version --snapshot canary + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Build + run: pnpm build + + - name: Run type check + run: pnpm test:types + + - name: Get version + id: version + run: | + VERSION=$(node -p "require('./package.json').version") + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Publishing version: $VERSION" + + - name: Determine npm tag + id: npm-tag + run: | + TYPE="${{ steps.publish-type.outputs.type }}" + VERSION="${{ steps.version.outputs.version }}" + + if [ "$TYPE" == "canary" ]; then + echo "npm_tag=canary" >> $GITHUB_OUTPUT + echo "is_prerelease=true" >> $GITHUB_OUTPUT + elif [[ "$VERSION" == *"-"* ]]; then + echo "npm_tag=beta" >> $GITHUB_OUTPUT + echo "is_prerelease=true" >> $GITHUB_OUTPUT + else + echo "npm_tag=latest" >> $GITHUB_OUTPUT + echo "is_prerelease=false" >> $GITHUB_OUTPUT + fi + + echo "npm tag: $(grep npm_tag $GITHUB_OUTPUT | tail -1)" + + - name: Pack tarball + run: | + mkdir -p .npm-pack + pnpm pack --pack-destination .npm-pack + echo "" + echo "Packed tarball:" + ls -la .npm-pack/ + + - name: 'Guard - Fail if tarball missing dist/ artifacts' + run: | + echo "Checking tarball for dist/ artifacts..." + + for tarball in .npm-pack/*.tgz; do + CONTENTS=$(tar -tzf "$tarball") + + if ! echo "$CONTENTS" | grep -q "package/dist/index.mjs"; then + echo "FAIL: ${tarball} is missing dist/index.mjs!" + exit 1 + fi + + if ! echo "$CONTENTS" | grep -q "package/dist/index.cjs"; then + echo "FAIL: ${tarball} is missing dist/index.cjs!" + exit 1 + fi + + if ! echo "$CONTENTS" | grep -q "package/dist/index.d.ts"; then + echo "FAIL: ${tarball} is missing dist/index.d.ts!" + exit 1 + fi + + if ! echo "$CONTENTS" | grep -q "package/dist/bin/index.cjs"; then + echo "FAIL: ${tarball} is missing dist/bin/index.cjs (CLI entrypoint)!" + exit 1 + fi + + echo "All required dist/ artifacts present" + done + + # --- Release only: tag + GitHub release --- + - name: Create and push tag + if: steps.publish-type.outputs.type == 'release' + run: | + TAG="v${{ steps.version.outputs.version }}" + if git rev-parse "$TAG" >/dev/null 2>&1; then + echo "Tag $TAG already exists, skipping" + else + git tag "$TAG" + echo "Created tag $TAG" + fi + git push origin "$TAG" 2>/dev/null || echo "Tag already pushed" + + - name: Publish to npm + if: steps.publish-type.outputs.type == 'canary' || github.event.inputs.publish-to-npm == 'true' + run: | + VERSION="${{ steps.version.outputs.version }}" + NPM_TAG="${{ steps.npm-tag.outputs.npm_tag }}" + TARBALL=$(ls .npm-pack/*.tgz | head -1) + + if [ -f "$TARBALL" ]; then + echo "Publishing ${TARBALL} with tag '${NPM_TAG}'..." + npm publish "$TARBALL" --access public --tag "$NPM_TAG" + echo "Published successfully" + else + echo "Tarball not found. Available:" + ls -la .npm-pack/ || true + exit 1 + fi + + - name: Create GitHub Release + if: steps.publish-type.outputs.type == 'release' && github.event.inputs.create-github-release == 'true' + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const tagName = `v${{ steps.version.outputs.version }}`; + const version = `${{ steps.version.outputs.version }}`; + const releaseName = `create-solana-dapp v${version}`; + const isPrerelease = '${{ steps.npm-tag.outputs.is_prerelease }}' === 'true'; + + const body = `## create-solana-dapp v${version} + + ### Installation + + \`\`\`bash + npx create-solana-dapp@${version} + \`\`\` + `; + + // Check if release already exists + try { + await github.rest.repos.getReleaseByTag({ + owner: context.repo.owner, + repo: context.repo.repo, + tag: tagName, + }); + console.log(`Release ${tagName} already exists, skipping`); + return; + } catch (e) { + if (e.status !== 404) throw e; + } + + await github.rest.repos.createRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + tag_name: tagName, + name: releaseName, + body: body.trim(), + draft: false, + prerelease: isPrerelease, + }); + + console.log(`Created release: ${releaseName}`); + + - name: Publish summary + run: | + TYPE="${{ steps.publish-type.outputs.type }}" + VERSION="${{ steps.version.outputs.version }}" + NPM_TAG="${{ steps.npm-tag.outputs.npm_tag }}" + + cat >> $GITHUB_STEP_SUMMARY << EOF + ## TypeScript Package Published + + **Type**: \`${TYPE}\` + **Version**: \`${VERSION}\` + **Package**: \`create-solana-dapp\` + **npm tag**: \`${NPM_TAG}\` + + EOF + + if [ "$TYPE" == "canary" ]; then + echo "Canary published to npm: \`create-solana-dapp@${NPM_TAG}\`" >> $GITHUB_STEP_SUMMARY + else + if [ "${{ github.event.inputs.publish-to-npm }}" == "true" ]; then + echo "Published to npm: https://www.npmjs.com/package/create-solana-dapp/v/${VERSION}" >> $GITHUB_STEP_SUMMARY + else + echo "Skipped npm publish (dry run)" >> $GITHUB_STEP_SUMMARY + fi + + if [ "${{ github.event.inputs.create-github-release }}" == "true" ]; then + echo "" >> $GITHUB_STEP_SUMMARY + echo "GitHub release created: https://github.com/${{ github.repository }}/releases/tag/v${VERSION}" >> $GITHUB_STEP_SUMMARY + else + echo "" >> $GITHUB_STEP_SUMMARY + echo "Skipped GitHub release creation" >> $GITHUB_STEP_SUMMARY + fi + fi diff --git a/.github/workflows/typescript-test.yml b/.github/workflows/typescript-test.yml new file mode 100644 index 00000000..e700d3d4 --- /dev/null +++ b/.github/workflows/typescript-test.yml @@ -0,0 +1,34 @@ +name: TypeScript Test + +on: + workflow_call: + +env: + DO_NOT_TRACK: '1' + TURBO_CONCURRENCY: 1 + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + node: + - 'current' + - 'lts/*' + + name: Test & Lint on Node ${{ matrix.node }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Dependencies + uses: ./.github/workflows/actions/install-dependencies + with: + version: ${{ matrix.node }} + + - name: Build + run: pnpm build + + - name: Test & Lint + run: pnpm test diff --git a/package.json b/package.json index 3feb49f9..86a91cbc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "create-solana-dapp", - "version": "4.8.2", + "version": "4.8.4", "description": "The fastest way to create Solana apps", "repository": { "name": "solana-foundation/create-solana-dapp", diff --git a/src/utils/create-app-task-install-dev-skill.ts b/src/utils/create-app-task-install-dev-skill.ts new file mode 100644 index 00000000..ea3f12d2 --- /dev/null +++ b/src/utils/create-app-task-install-dev-skill.ts @@ -0,0 +1,29 @@ +import { log } from '@clack/prompts' +import { GetArgsResult } from './get-args-result' +import { execAndWait } from './vendor/child-process-utils' +import { Task } from './vendor/clack-tasks' + +export function createAppTaskInstallDevSkill(args: GetArgsResult): Task { + return { + enabled: !args.skipInstall, + task: async (result) => { + try { + if (args.verbose) { + log.warn(`Installing Solana dev skill`) + } + await execAndWait( + 'npx -y skills add https://github.com/solana-foundation/solana-dev-skill --skill "*" -y', + args.targetDirectory, + ) + return result({ message: 'Installed Solana dev skill' }) + } catch (error) { + if (args.verbose) { + log.error(`Error installing Solana dev skill: ${error}`) + } + // Non-fatal: skip if it fails + return result({ message: 'Skipped Solana dev skill installation' }) + } + }, + title: 'Installing Solana dev skill', + } +} diff --git a/src/utils/create-app.ts b/src/utils/create-app.ts index 36b5fcea..f02af1c5 100644 --- a/src/utils/create-app.ts +++ b/src/utils/create-app.ts @@ -1,6 +1,7 @@ import { createAppTaskCloneTemplate } from './create-app-task-clone-template' import { createAppTaskInitializeGit } from './create-app-task-initialize-git' import { createAppTaskInstallDependencies } from './create-app-task-install-dependencies' +import { createAppTaskInstallDevSkill } from './create-app-task-install-dev-skill' import { createAppTaskRunInitScript } from './create-app-task-run-init-script' import { createAppTaskRunSetup } from './create-app-task-run-setup' import { GetArgsResult } from './get-args-result' @@ -16,6 +17,8 @@ export async function createApp(args: GetArgsResult) { createAppTaskRunSetup(args), // Run the (optional) init script defined in package.json createAppTaskRunInitScript(args), + // Install the Solana dev skill for AI coding agents + createAppTaskInstallDevSkill(args), // Initialize git repository createAppTaskInitializeGit(args), ]) diff --git a/src/utils/vendor/git.ts b/src/utils/vendor/git.ts index 53d9a7e1..41a5f3f1 100644 --- a/src/utils/vendor/git.ts +++ b/src/utils/vendor/git.ts @@ -54,7 +54,7 @@ export async function initializeGitRepo(directory: string, verbose = false) { GIT_COMMITTER_EMAIL: email, GIT_COMMITTER_NAME: name, }, - shell: true, + shell: false, stdio: [process.stdin, outputStream, errorStream], } return new Promise((resolve, reject) => { @@ -90,5 +90,5 @@ export async function initializeGitRepo(directory: string, verbose = false) { } await execute(['add', '.']) const message = 'chore: initial commit' - await execute(['commit', `-m "${message}"`]) + await execute(['commit', '-m', message]) }