From 1ee6d915de9f8f16ecf8a55ca2b0694f3614f414 Mon Sep 17 00:00:00 2001 From: Tanay Karnik Date: Thu, 6 Mar 2025 18:10:18 +0530 Subject: [PATCH 01/13] fix: install deps --- .github/workflows/nuxflare-deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/nuxflare-deploy.yml b/.github/workflows/nuxflare-deploy.yml index 63ba764..6bf7ef5 100644 --- a/.github/workflows/nuxflare-deploy.yml +++ b/.github/workflows/nuxflare-deploy.yml @@ -48,6 +48,7 @@ jobs: - name: Build Nuxflare working-directory: . run: | + pnpm i pnpm run build - name: Install deps From 627b0519f8fffff1b733b22dea559a55d182c747 Mon Sep 17 00:00:00 2001 From: Tanay Karnik Date: Thu, 6 Mar 2025 18:15:45 +0530 Subject: [PATCH 02/13] debug --- src/commands/deploy.ts | 1 + src/init/sst/sst.config.ts | 5 ++--- src/utils/sst.ts | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/commands/deploy.ts b/src/commands/deploy.ts index e2b03b0..eab0559 100644 --- a/src/commands/deploy.ts +++ b/src/commands/deploy.ts @@ -40,6 +40,7 @@ async function displayProjectUrls(stage: string) { } } } catch (error) { + console.log("error project uurls", error) // If there's an error reading the directory, just skip URL display console.error("Unable to read deployment URLs"); } diff --git a/src/init/sst/sst.config.ts b/src/init/sst/sst.config.ts index ff7a93d..da798e9 100644 --- a/src/init/sst/sst.config.ts +++ b/src/init/sst/sst.config.ts @@ -9,7 +9,6 @@ export default $config({ return { name: "__PROJECT_NAME__", removal: input?.stage === "production" ? "retain" : "remove", - protect: ["production"].includes(input?.stage), home: "cloudflare", providers: { cloudflare: true, @@ -23,8 +22,8 @@ export default $config({ $app.stage === "production" ? prodDomain || undefined : devDomain - ? `${$app.stage}.${devDomain}` - : undefined; + ? `${$app.stage}.${devDomain}` + : undefined; Nuxt("App", { dir: ".", domain, diff --git a/src/utils/sst.ts b/src/utils/sst.ts index eac1f9a..492e88b 100644 --- a/src/utils/sst.ts +++ b/src/utils/sst.ts @@ -38,6 +38,7 @@ export async function executeSST( if (code === 0) { resolve(); } else { + console.error("is this it") reject(new Error(`Process exited with code ${code}`)); } }); From 7bdd540b9bf5f8ab24c001af6c7c4ac7b5691237 Mon Sep 17 00:00:00 2001 From: Tanay Karnik Date: Thu, 6 Mar 2025 18:20:48 +0530 Subject: [PATCH 03/13] debug --- .github/workflows/nuxflare-deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/nuxflare-deploy.yml b/.github/workflows/nuxflare-deploy.yml index 6bf7ef5..06fb87f 100644 --- a/.github/workflows/nuxflare-deploy.yml +++ b/.github/workflows/nuxflare-deploy.yml @@ -67,6 +67,7 @@ jobs: STAGE="${{ github.event.inputs.stage }}" else # Sanitize branch name: lower-case and replace non-alphanumeric chars with dashes + echo "${GITHUB_REF_NAME} and ${GITHUB_REF} and ${GITHUB_EVENT_NAME}" STAGE=$(echo "${GITHUB_REF_NAME}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g') fi echo "Deploying to stage: ${STAGE}" From ae41d596b45b09d6f4c39382f77f277ffe275f3a Mon Sep 17 00:00:00 2001 From: Tanay Karnik Date: Thu, 6 Mar 2025 18:29:07 +0530 Subject: [PATCH 04/13] teset --- src/init/action.yml.liquid | 2 ++ src/utils/sst.ts | 31 +++---------------------------- 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/src/init/action.yml.liquid b/src/init/action.yml.liquid index 7b310c3..2d1508b 100644 --- a/src/init/action.yml.liquid +++ b/src/init/action.yml.liquid @@ -42,6 +42,8 @@ jobs: {% elsif package_manager == "pnpm" %} - name: Install pnpm uses: pnpm/action-setup@v4 + with: + version: 9.15.0 - name: Set up Node.js uses: actions/setup-node@v4 diff --git a/src/utils/sst.ts b/src/utils/sst.ts index 492e88b..83624e9 100644 --- a/src/utils/sst.ts +++ b/src/utils/sst.ts @@ -24,34 +24,9 @@ export async function executeSST( const stdio = options.stdio || "pipe"; const cwd = options.cwd || process.cwd(); const env = options.env ? { ...process.env, ...options.env } : process.env; - - if (stdio === "inherit") { - return new Promise((resolve, reject) => { - const childProcess = spawn(command, args, { - stdio, - cwd, - env, - shell: true, - }); - - childProcess.on("close", (code) => { - if (code === 0) { - resolve(); - } else { - console.error("is this it") - reject(new Error(`Process exited with code ${code}`)); - } - }); - - childProcess.on("error", (err) => { - reject(err); - }); - }); - } else { - return execSync(`${command} ${args.join(" ")}`, { stdio, cwd, env }) - .toString() - .trim(); - } + return execSync(`${command} ${args.join(" ")}`, { stdio, cwd, env }) + .toString() + .trim(); } catch (error) { if (error instanceof Error) { throw new Error(`Failed to execute SST command: ${error.message}`); From b1a933bed1a404333abef69ad9b4f2e78ca122b4 Mon Sep 17 00:00:00 2001 From: Tanay Karnik Date: Thu, 6 Mar 2025 18:32:59 +0530 Subject: [PATCH 05/13] asdf --- src/commands/deploy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/deploy.ts b/src/commands/deploy.ts index eab0559..fbdbefa 100644 --- a/src/commands/deploy.ts +++ b/src/commands/deploy.ts @@ -88,7 +88,7 @@ export async function deploy(options: DeployOptions = {}) { log.step(`Deploying to stage: ${deployStage}`); try { - await executeSST(["deploy", "--stage", deployStage], { + await executeSST(["deploy", "--stage", deployStage, "--verbose"], { stdio: "inherit", env: { NITRO_PRESET: "cloudflare-module", From 899b950953289ec8580feb0dfea484031f73540f Mon Sep 17 00:00:00 2001 From: Tanay Karnik Date: Thu, 6 Mar 2025 18:41:12 +0530 Subject: [PATCH 06/13] asdf --- .github/workflows/nuxflare-deploy.yml | 2 +- src/commands/init.ts | 20 +++++++++++++++----- src/init/action.yml.liquid | 11 ++++++++++- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/.github/workflows/nuxflare-deploy.yml b/.github/workflows/nuxflare-deploy.yml index 06fb87f..14ee8cb 100644 --- a/.github/workflows/nuxflare-deploy.yml +++ b/.github/workflows/nuxflare-deploy.yml @@ -75,4 +75,4 @@ jobs: fi echo "Running: ${DEPLOY_CMD}" - node ../dist/index.js ${DEPLOY_CMD} + SST_STAGE=$STAGE pnpm exec sst deploy diff --git a/src/commands/init.ts b/src/commands/init.ts index 475c923..fd5cfd2 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -86,9 +86,16 @@ export async function init() { placeholder: "e.g., dev.example.codes (stages like 'dev' will deploy to 'dev.dev.example.codes')", }), - setupGithubActions: () => - p.confirm({ - message: "Would you like to setup GitHub Actions?", + githubActions: () => + p.select({ + message: "How would you like to setup GitHub Actions?", + options: [ + { value: "none", label: "Don't setup GitHub Actions" }, + { value: "manual", label: "Manual deployments only (via workflow dispatch)" }, + { value: "prod", label: "Automatic production deployments only (main branch)" }, + { value: "full", label: "Full setup (automatic prod and preview deployments for PRs)" }, + ], + initialValue: "none", }), }, { @@ -161,7 +168,7 @@ export async function init() { { title: "Setting up GitHub Actions", task: async () => { - if (!results.setupGithubActions) { + if (results.githubActions === "none") { return "GitHub Actions setup skipped"; } @@ -171,7 +178,10 @@ export async function init() { // Copy and render the action.yml template const engine = new Liquid() const template = engine.parse(await fs.readFile(path.join(initDir, "action.yml.liquid"), "utf8")); - const actionContent = await engine.render(template, { package_manager: results.packageManager }); + const actionContent = await engine.render(template, { + package_manager: results.packageManager, + github_action_type: results.githubActions + }); await fs.writeFile( path.join(githubDir, "nuxflare-deploy.yml"), diff --git a/src/init/action.yml.liquid b/src/init/action.yml.liquid index 2d1508b..8da0df7 100644 --- a/src/init/action.yml.liquid +++ b/src/init/action.yml.liquid @@ -1,8 +1,14 @@ name: "Nuxflare Deploy" on: + {% if github_action_type == "full" %} push: pull_request: + {% elsif github_action_type == "prod" %} + push: + branches: + - main + {% endif %} workflow_dispatch: inputs: stage: @@ -15,7 +21,10 @@ concurrency: jobs: deploy: - if: github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == 'refs/heads/main') + {% if github_action_type == "full" %}if: github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == 'refs/heads/main') + {% elsif github_action_type == "prod" %}if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/main') + {% else %}if: github.event_name == 'workflow_dispatch' + {% endif %} runs-on: ubuntu-latest permissions: contents: read From 9352adc043382dcb81766bbd0bbf28aba43bdec9 Mon Sep 17 00:00:00 2001 From: Tanay Karnik Date: Thu, 6 Mar 2025 18:45:09 +0530 Subject: [PATCH 07/13] debug --- .github/workflows/nuxflare-deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/nuxflare-deploy.yml b/.github/workflows/nuxflare-deploy.yml index 14ee8cb..2ffe092 100644 --- a/.github/workflows/nuxflare-deploy.yml +++ b/.github/workflows/nuxflare-deploy.yml @@ -75,4 +75,5 @@ jobs: fi echo "Running: ${DEPLOY_CMD}" + SST_STAGE=$STAGE pnpm exec sst install SST_STAGE=$STAGE pnpm exec sst deploy From fa7a85805d51d9fc8f59af1f862669d1463897e8 Mon Sep 17 00:00:00 2001 From: Tanay Karnik Date: Thu, 6 Mar 2025 18:59:50 +0530 Subject: [PATCH 08/13] asdf --- .github/workflows/nuxflare-deploy.yml | 7 +++-- src/commands/init.ts | 40 +++++++++++++++++---------- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/.github/workflows/nuxflare-deploy.yml b/.github/workflows/nuxflare-deploy.yml index 2ffe092..ab6787c 100644 --- a/.github/workflows/nuxflare-deploy.yml +++ b/.github/workflows/nuxflare-deploy.yml @@ -54,6 +54,9 @@ jobs: - name: Install deps run: pnpm install --frozen-lockfile + - name: Install SST + run: "curl -fsSL https://ion.sst.dev/install | bash" + - name: Deploy env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} @@ -75,5 +78,5 @@ jobs: fi echo "Running: ${DEPLOY_CMD}" - SST_STAGE=$STAGE pnpm exec sst install - SST_STAGE=$STAGE pnpm exec sst deploy + SST_STAGE=$STAGE sst install + SST_STAGE=$STAGE sst deploy diff --git a/src/commands/init.ts b/src/commands/init.ts index fd5cfd2..f91d03b 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -227,21 +227,33 @@ export async function init() { log.success(chalk.green("✅ Successfully initialized Nuxflare!")); + const nextSteps = [ + `1. Run ${chalk.cyan( + "nuxflare deploy --stage ", + )} to do a preview deployment.`, + `2. Run ${chalk.cyan( + "nuxflare deploy --production", + )} to deploy to production.`, + `3. Run ${chalk.cyan( + "nuxflare dev --stage ", + )} to run a local dev server and connect to remote resources.`, + `4. Run ${chalk.cyan( + "nuxflare copy-env --stage --file .env", + )} to copy environment variables from a .env file to a stage.` + ]; + + if (results.githubActions !== "none") { + nextSteps.push( + `5. ${chalk.yellow("Important:")} Review your GitHub Actions workflow in ${chalk.cyan( + ".github/workflows/nuxflare-deploy.yml" + )} and add your ${chalk.cyan( + "CLOUDFLARE_API_TOKEN" + )} to your repository secrets by going to Settings > Secrets and variables > Actions > New repository secret.` + ); + } + p.note( - [ - `1. Run ${chalk.cyan( - "nuxflare deploy --stage ", - )} to do a preview deployment.`, - `2. Run ${chalk.cyan( - "nuxflare deploy --production", - )} to deploy to production.`, - `3. Run ${chalk.cyan( - "nuxflare dev --stage ", - )} to run a local dev server and connect to remote resources.`, - `4. Run ${chalk.cyan( - "nuxflare copy-env --stage --file .env", - )} to copy environment variables from a .env file to a stage.` - ].join("\n"), + nextSteps.join("\n"), "Next steps", ); } catch (error) { From 1c48cdc94757670c650e939086a6918e5de1b131 Mon Sep 17 00:00:00 2001 From: Tanay Karnik Date: Thu, 6 Mar 2025 19:06:31 +0530 Subject: [PATCH 09/13] asdf --- playground/package.json | 1 - playground/pnpm-lock.yaml | 3 --- 2 files changed, 4 deletions(-) diff --git a/playground/package.json b/playground/package.json index e80d6fb..a72f562 100644 --- a/playground/package.json +++ b/playground/package.json @@ -32,7 +32,6 @@ "drizzle-kit": "^0.30.0", "eslint": "^9.16.0", "nuxthub-ratelimit": "^1.0.4", - "sst": "^3.9.26", "vue-tsc": "^2.1.10", "wrangler": "^3.95.0", "zod": "^3.24.1" diff --git a/playground/pnpm-lock.yaml b/playground/pnpm-lock.yaml index ef1569b..8f98224 100644 --- a/playground/pnpm-lock.yaml +++ b/playground/pnpm-lock.yaml @@ -57,9 +57,6 @@ importers: nuxthub-ratelimit: specifier: ^1.0.4 version: 1.0.4(magicast@0.3.5)(rollup@4.28.1) - sst: - specifier: ^3.9.26 - version: 3.9.26 vue-tsc: specifier: ^2.1.10 version: 2.1.10(typescript@5.7.2) From 5a03164cc1285359f5e860a05aa436221836a67a Mon Sep 17 00:00:00 2001 From: Tanay Karnik Date: Thu, 6 Mar 2025 19:16:57 +0530 Subject: [PATCH 10/13] remove cache --- .github/workflows/nuxflare-deploy.yml | 6 ----- playground/sst.config.ts | 38 +++++++++++++-------------- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/.github/workflows/nuxflare-deploy.yml b/.github/workflows/nuxflare-deploy.yml index ab6787c..3fc9b38 100644 --- a/.github/workflows/nuxflare-deploy.yml +++ b/.github/workflows/nuxflare-deploy.yml @@ -28,12 +28,6 @@ jobs: with: fetch-depth: 0 - - uses: actions/cache@v4 - with: - path: | - playground/.sst - key: ${{ runner.os }}-sst-playground - - name: Install pnpm uses: pnpm/action-setup@v4 with: diff --git a/playground/sst.config.ts b/playground/sst.config.ts index 1392207..33af7dd 100644 --- a/playground/sst.config.ts +++ b/playground/sst.config.ts @@ -1,34 +1,34 @@ /// -import Nuxt from "./.nuxflare/utils/nuxt"; +import Nuxt from './.nuxflare/utils/nuxt' -const prodDomain = "test.tanay.codes"; -const devDomain = "chat.tanay.codes"; +const prodDomain = 'test.tanay.codes' +const devDomain = 'chat.tanay.codes' export default $config({ app(input) { return { - name: "tanay-chat", - removal: input?.stage === "production" ? "retain" : "remove", - protect: ["production"].includes(input?.stage), - home: "cloudflare", + name: 'tanay-chat', + removal: input?.stage === 'production' ? 'retain' : 'remove', + protect: ['production'].includes(input?.stage), + home: 'cloudflare', providers: { cloudflare: true, - command: "1.0.1", - random: "4.17.0", + command: '1.0.1', + random: '4.17.0', }, - }; + } }, async run() { - const domain = - $app.stage === "production" + const domain + = $app.stage === 'production' ? prodDomain || undefined : devDomain - ? `${$app.stage}.${devDomain}` - : undefined; - Nuxt("App", { - dir: ".", + ? `${$app.stage}.${devDomain}` + : undefined + Nuxt('App', { + dir: '.', domain, - packageManager: "pnpm", - }); + packageManager: 'pnpm', + }) }, -}); +}) From cc101940f0da83b97c38dc5ceaa6be07b7a11119 Mon Sep 17 00:00:00 2001 From: Tanay Karnik Date: Thu, 6 Mar 2025 19:32:16 +0530 Subject: [PATCH 11/13] fix --- .github/workflows/nuxflare-deploy.yml | 6 +- .../utils => playground/nuxflare}/builder.ts | 0 .../utils => playground/nuxflare}/nuxt.ts | 0 .../nuxflare}/semaphore.ts | 0 playground/package.json | 1 + playground/pnpm-lock.yaml | 49 +-- playground/sst.config.ts | 35 +- src/commands/init.ts | 4 +- src/init/sst/nuxflare/builder.ts | 32 ++ src/init/sst/nuxflare/nuxt.ts | 390 ++++++++++++++++++ src/init/sst/nuxflare/semaphore.ts | 29 ++ src/init/sst/sst.config.ts | 2 +- 12 files changed, 499 insertions(+), 49 deletions(-) rename {src/init/sst/.nuxflare/utils => playground/nuxflare}/builder.ts (100%) rename {src/init/sst/.nuxflare/utils => playground/nuxflare}/nuxt.ts (100%) rename {src/init/sst/.nuxflare/utils => playground/nuxflare}/semaphore.ts (100%) create mode 100644 src/init/sst/nuxflare/builder.ts create mode 100644 src/init/sst/nuxflare/nuxt.ts create mode 100644 src/init/sst/nuxflare/semaphore.ts diff --git a/.github/workflows/nuxflare-deploy.yml b/.github/workflows/nuxflare-deploy.yml index 3fc9b38..697b651 100644 --- a/.github/workflows/nuxflare-deploy.yml +++ b/.github/workflows/nuxflare-deploy.yml @@ -48,9 +48,6 @@ jobs: - name: Install deps run: pnpm install --frozen-lockfile - - name: Install SST - run: "curl -fsSL https://ion.sst.dev/install | bash" - - name: Deploy env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} @@ -72,5 +69,4 @@ jobs: fi echo "Running: ${DEPLOY_CMD}" - SST_STAGE=$STAGE sst install - SST_STAGE=$STAGE sst deploy + npx ${DEPLOY_CMD} diff --git a/src/init/sst/.nuxflare/utils/builder.ts b/playground/nuxflare/builder.ts similarity index 100% rename from src/init/sst/.nuxflare/utils/builder.ts rename to playground/nuxflare/builder.ts diff --git a/src/init/sst/.nuxflare/utils/nuxt.ts b/playground/nuxflare/nuxt.ts similarity index 100% rename from src/init/sst/.nuxflare/utils/nuxt.ts rename to playground/nuxflare/nuxt.ts diff --git a/src/init/sst/.nuxflare/utils/semaphore.ts b/playground/nuxflare/semaphore.ts similarity index 100% rename from src/init/sst/.nuxflare/utils/semaphore.ts rename to playground/nuxflare/semaphore.ts diff --git a/playground/package.json b/playground/package.json index a72f562..685ea5c 100644 --- a/playground/package.json +++ b/playground/package.json @@ -32,6 +32,7 @@ "drizzle-kit": "^0.30.0", "eslint": "^9.16.0", "nuxthub-ratelimit": "^1.0.4", + "sst": "^3.9.27", "vue-tsc": "^2.1.10", "wrangler": "^3.95.0", "zod": "^3.24.1" diff --git a/playground/pnpm-lock.yaml b/playground/pnpm-lock.yaml index 8f98224..9fe7ade 100644 --- a/playground/pnpm-lock.yaml +++ b/playground/pnpm-lock.yaml @@ -57,6 +57,9 @@ importers: nuxthub-ratelimit: specifier: ^1.0.4 version: 1.0.4(magicast@0.3.5)(rollup@4.28.1) + sst: + specifier: ^3.9.27 + version: 3.9.27 vue-tsc: specifier: ^2.1.10 version: 2.1.10(typescript@5.7.2) @@ -4966,33 +4969,33 @@ packages: resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} engines: {node: '>=0.10.0'} - sst-darwin-arm64@3.9.26: - resolution: {integrity: sha512-78GSe3obviQV3PycSo4zUfF7k8IxjS4LmHJwunFlVc4z9AzCU7F2au12+9O5jxWraVK/EnYCN/MhRMi54qMqfA==} + sst-darwin-arm64@3.9.27: + resolution: {integrity: sha512-eDmSOFoW2EXzM8+CfqW0n+0bb5gFCSNkAS/wcwMT3iyAPPJANtWXAyFYUsXbLgXjIpg6uofMTbEGb4T3gEmpJg==} cpu: [arm64] os: [darwin] - sst-darwin-x64@3.9.26: - resolution: {integrity: sha512-sOQUZCz/CcCBm5wRyRdaCe7LnO+imrgxHSgq8x4zCWBD5snElDamRtbUe7kw+3zd2ckU/fp34bDP4KfzTKARGw==} + sst-darwin-x64@3.9.27: + resolution: {integrity: sha512-qRfXwBtCZfI1lHSlvPdt9wex3uTRCTE2e4tCQ749mtY3s0mdSYQ4ikdD1/7uxpjVxTwve705jfQBNpa4Pn6DIg==} cpu: [x64] os: [darwin] - sst-linux-arm64@3.9.26: - resolution: {integrity: sha512-IpUA7eLjSwAvKMx92P+CJEuIZPKsDo9t/khSCoAWwkKq1EN80hV9etPhm1/jQCdB0PERoggGXT2oL/IJTLtb2g==} + sst-linux-arm64@3.9.27: + resolution: {integrity: sha512-BbGM59rYcBdwHLUsp3++VkbtZABxon4eYmgrc7v8iP3CrpbeoWn1VCP1MnFoXdXvx9n6Nbmui3oATQUVeWgBFg==} cpu: [arm64] os: [linux] - sst-linux-x64@3.9.26: - resolution: {integrity: sha512-tMNDQBSFGAFcX9Zc/9W8hsd8n9cp2vx6rMjko2sV3wbF/K0dledKGjT5EhzDAlccP6bFS3XGiIzsUfhsgZW4Ag==} + sst-linux-x64@3.9.27: + resolution: {integrity: sha512-ZSBmYryaXkQVgVfbPx/ZlPqZXQTX2aGnbCAQ91vkKnhDx7kaF84zAx7bIvk9Jk/1ZbPg5HTvoKm0CWFgJjKKxg==} cpu: [x64] os: [linux] - sst-linux-x86@3.9.26: - resolution: {integrity: sha512-ll1koUZ4D6peMxR8Th5DSY5UZvLXaAWmCA7vC9la0KnQZz/odJBeY2CZRwUXyntpyoGRvFJXWKhecbOoaVyzhA==} + sst-linux-x86@3.9.27: + resolution: {integrity: sha512-aMw7b+SJjPrlSqasJjkIIf6Saz3HVFugo1XDoCW8rNIgwfzVX7/gT2ds87KefpCOYdMARICcKuCycQq1nT+4jg==} cpu: [x86] os: [linux] - sst@3.9.26: - resolution: {integrity: sha512-LWDRA+sYcc4cD+1sXp4RW5asVDCzrWVyqE0L1FdWxQ8vV/BN9/5TpEFulShTkom1s/CmuLh5H6+qOGO5Di7XqQ==} + sst@3.9.27: + resolution: {integrity: sha512-kt9Lw65MhqSwIJBywBalhZrj3FFLnKHn9KcVI4FuoLXpek+0tvQKW2mnwxsO9pqF96a8sYtlNRki6F1neb9qgg==} hasBin: true stable-hash@0.0.4: @@ -11470,32 +11473,32 @@ snapshots: speakingurl@14.0.1: {} - sst-darwin-arm64@3.9.26: + sst-darwin-arm64@3.9.27: optional: true - sst-darwin-x64@3.9.26: + sst-darwin-x64@3.9.27: optional: true - sst-linux-arm64@3.9.26: + sst-linux-arm64@3.9.27: optional: true - sst-linux-x64@3.9.26: + sst-linux-x64@3.9.27: optional: true - sst-linux-x86@3.9.26: + sst-linux-x86@3.9.27: optional: true - sst@3.9.26: + sst@3.9.27: dependencies: aws4fetch: 1.0.20 jose: 5.2.3 openid-client: 5.6.4 optionalDependencies: - sst-darwin-arm64: 3.9.26 - sst-darwin-x64: 3.9.26 - sst-linux-arm64: 3.9.26 - sst-linux-x64: 3.9.26 - sst-linux-x86: 3.9.26 + sst-darwin-arm64: 3.9.27 + sst-darwin-x64: 3.9.27 + sst-linux-arm64: 3.9.27 + sst-linux-x64: 3.9.27 + sst-linux-x86: 3.9.27 stable-hash@0.0.4: {} diff --git a/playground/sst.config.ts b/playground/sst.config.ts index 33af7dd..9087bff 100644 --- a/playground/sst.config.ts +++ b/playground/sst.config.ts @@ -1,34 +1,33 @@ /// -import Nuxt from './.nuxflare/utils/nuxt' +import Nuxt from "./nuxflare/nuxt"; -const prodDomain = 'test.tanay.codes' -const devDomain = 'chat.tanay.codes' +const prodDomain = "chat.tanay.codes"; +const devDomain = undefined; export default $config({ app(input) { return { - name: 'tanay-chat', - removal: input?.stage === 'production' ? 'retain' : 'remove', - protect: ['production'].includes(input?.stage), - home: 'cloudflare', + name: "tanay-chat", + removal: input?.stage === "production" ? "retain" : "remove", + home: "cloudflare", providers: { cloudflare: true, - command: '1.0.1', - random: '4.17.0', + command: "1.0.1", + random: "4.17.0", }, - } + }; }, async run() { - const domain - = $app.stage === 'production' + const domain = + $app.stage === "production" ? prodDomain || undefined : devDomain ? `${$app.stage}.${devDomain}` - : undefined - Nuxt('App', { - dir: '.', + : undefined; + Nuxt("App", { + dir: ".", domain, - packageManager: 'pnpm', - }) + packageManager: "pnpm", + }); }, -}) +}); diff --git a/src/commands/init.ts b/src/commands/init.ts index f91d03b..caab477 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -178,7 +178,7 @@ export async function init() { // Copy and render the action.yml template const engine = new Liquid() const template = engine.parse(await fs.readFile(path.join(initDir, "action.yml.liquid"), "utf8")); - const actionContent = await engine.render(template, { + const actionContent = await engine.render(template, { package_manager: results.packageManager, github_action_type: results.githubActions }); @@ -241,7 +241,7 @@ export async function init() { "nuxflare copy-env --stage --file .env", )} to copy environment variables from a .env file to a stage.` ]; - + if (results.githubActions !== "none") { nextSteps.push( `5. ${chalk.yellow("Important:")} Review your GitHub Actions workflow in ${chalk.cyan( diff --git a/src/init/sst/nuxflare/builder.ts b/src/init/sst/nuxflare/builder.ts new file mode 100644 index 0000000..74c974f --- /dev/null +++ b/src/init/sst/nuxflare/builder.ts @@ -0,0 +1,32 @@ +import * as path from "node:path"; +import { Semaphore } from "./semaphore"; + +const limiter = new Semaphore( + parseInt(process.env.SST_BUILD_CONCURRENCY_SITE || "1"), +); +export async function builder( + name: string, + { + dir, + env, + packageManager, + }: { dir: string; env?: Record; packageManager: string }, +): Promise { + return new Promise((res) => { + $resolve({ env }).apply(async ({ env }) => { + // acquire semaphore after only resolving environment and we are ready to build + await limiter.acquire(); + const cmd = new command.local.Command(`${name}Build`, { + dir: path.resolve(dir), + create: `[ -z "$SKIP_BUILD" ] && NITRO_PRESET=cloudflare-module ${packageManager} run build || ( [ -n "$SKIP_BUILD" ] && echo "Skipping build." )`, + update: `[ -z "$SKIP_BUILD" ] && NITRO_PRESET=cloudflare-module ${packageManager} run build || ( [ -n "$SKIP_BUILD" ] && echo "Skipping build." )`, + triggers: [new Date().toString()], + environment: env, + }); + cmd.urn.apply(() => { + limiter.release(); + res(cmd); + }); + }); + }); +} diff --git a/src/init/sst/nuxflare/nuxt.ts b/src/init/sst/nuxflare/nuxt.ts new file mode 100644 index 0000000..be8b5aa --- /dev/null +++ b/src/init/sst/nuxflare/nuxt.ts @@ -0,0 +1,390 @@ +import { writeFileSync, mkdirSync, existsSync } from "node:fs"; +import * as path from "node:path"; +import { loadNuxtConfig } from "nuxt/kit"; +import { builder } from "./builder"; + +const DEFAULT_CLOUDFLARE_COMPATIBILITY_DATE = "2024-12-05"; +const BINDINGS = { + ASSETS: "ASSETS", + DATABASE: "DB", + AI: "AI", + KV: "KV", + CACHE: "CACHE", + BLOB: "BLOB", +} as const; + +const PACKAGE_MANAGER_COMMANDS = { + pnpm: "pnpm exec", + yarn: "yarn exec", + npm: "npx", + bun: "bun x", +} as const; + +type DatabaseConfig = { id: string; name: string } | sst.cloudflare.D1; + +class WranglerConfigBuilder { + private config: Record = {}; + + constructor(name: string, outputDir: string, compatibilityDate?: string) { + this.config = { + name: name.toLowerCase(), + main: path.resolve(outputDir, "server", "index.mjs"), + compatibility_date: + compatibilityDate || DEFAULT_CLOUDFLARE_COMPATIBILITY_DATE, + compatibility_flags: ["nodejs_compat"], + observability: { enabled: true }, + }; + } + + addAssets(publicDir: string): this { + this.config.assets = { + directory: publicDir, + binding: BINDINGS.ASSETS, + }; + return this; + } + + addDomain(domain?: string): this { + if (domain) { + this.config.routes = [ + { + pattern: domain, + custom_domain: true, + }, + ]; + } + return this; + } + + addD1Database(database: DatabaseConfig, migrationsDir: string): this { + this.config.d1_databases ||= []; + if (database instanceof sst.cloudflare.D1) { + this.config.d1_databases.push({ + binding: BINDINGS.DATABASE, + database_name: database.nodes.database.name, + database_id: database.id, + migrations_dir: migrationsDir, + }); + } else { + this.config.d1_databases.push({ + binding: BINDINGS.DATABASE, + database_name: database.name, + database_id: database.id, + migrations_dir: migrationsDir, + }); + } + return this; + } + + addAI(): this { + this.config.ai = { + binding: BINDINGS.AI, + }; + return this; + } + + addKV(kv: sst.cloudflare.Kv, binding: string): this { + this.config.kv_namespaces ||= []; + this.config.kv_namespaces.push({ + binding, + id: kv.id, + }); + return this; + } + + addBucket(blob: sst.cloudflare.Bucket): this { + this.config.r2_buckets ||= []; + this.config.r2_buckets.push({ + binding: BINDINGS.BLOB, + bucket_name: blob.name, + }); + return this; + } + + addVectorize(name: string, indexName: any): this { + this.config.vectorize ||= []; + this.config.vectorize.push({ + binding: `VECTORIZE_${name.toUpperCase()}`, + index_name: indexName, + }); + return this; + } + + addVars( + environment: sst.Secret, + extraVars: Record, + nuxtHubSecret: typeof random.RandomUuid.prototype.result, + ): this { + this.config.vars = environment.value.apply((env) => { + let parsedEnv = {}; + try { + parsedEnv = JSON.parse(env); + } catch (error) { + console.warn("Failed to parse environment:", error); + } + return { + NUXT_HUB_PROJECT_SECRET_KEY: nuxtHubSecret, + ...parsedEnv, + ...extraVars, + }; + }); + return this; + } + + async transform( + transformFn?: (config: Record) => Promise | void, + ): Promise { + if (transformFn) { + await transformFn(this.config); + } + return this; + } + + build(): Record { + return this.config; + } +} + +/** + * Creates and configures a Nuxt.js application for Cloudflare Workers deployment + * + * @param name - Application name (must start with a capital letter, no hyphens) + * @param options - Configuration options for the deployment + * @param options.dir - Root directory of the Nuxt application + * @param options.domain - Optional domain/subdomain. If not specified, automatic workers.dev subdomain is used + * @param options.extraVars - Additional environment variables used in binding and building the Nuxt app + * @param options.transformWrangler - Optional function to modify the wrangler configuration before deployment + * @param options.packageManager - Package manager to use for building the app and running wrangler + * @param options.compatibilityDate - Optionally specify the Cloudflare Workers compatibility date + * @param options.database - Optional database configuration (existing or SST database) + * @returns Promise that resolves when deployment is configured + */ +async function Nuxt( + name: string, + { + dir, + domain, + extraVars = {}, + transformWrangler, + packageManager = "pnpm", + database, + compatibilityDate, + }: { + dir: string; + domain?: string; + extraVars?: Record; + transformWrangler?: ( + wrangler: Record, + ) => Promise | void; + packageManager?: keyof typeof PACKAGE_MANAGER_COMMANDS; + database?: DatabaseConfig; + compatibilityDate?: string; + }, +) { + const packageManagerX = PACKAGE_MANAGER_COMMANDS[packageManager]; + const projectPath = path.resolve(dir); + const environment = new sst.Secret("Env", "{}"); + const nuxtConfig = await loadNuxtConfig({ cwd: projectPath }); + const hubConfig = nuxtConfig.hub || {}; + const nuxtHubSecret = new random.RandomUuid(`${name}NuxtHubSecret`); + const buildOutputPath = path.resolve(projectPath, "dist"); + const migrationsPath = path.resolve( + buildOutputPath, + "database", + "migrations", + ); + + const wranglerBuilder = new WranglerConfigBuilder( + `${$app.name}-${$app.stage}-${name}`, + buildOutputPath, + compatibilityDate, + ) + .addAssets(path.resolve(buildOutputPath, "public")) + .addDomain(domain) + .addVars(environment, extraVars, nuxtHubSecret.result); + + const resources: any[] = []; + const databaseConfig: { name?: any } = {}; + + if (hubConfig.database) { + if (database instanceof sst.cloudflare.D1) { + resources.push(database); + wranglerBuilder.addD1Database(database, migrationsPath); + databaseConfig.name = database.nodes.database.name; + } else if (database) { + wranglerBuilder.addD1Database(database, migrationsPath); + databaseConfig.name = database.name; + } else { + const database = new sst.cloudflare.D1(BINDINGS.DATABASE); + resources.push(database); + wranglerBuilder.addD1Database(database, migrationsPath); + databaseConfig.name = database.nodes.database.name; + } + } + + if (hubConfig.ai) { + wranglerBuilder.addAI(); + } + + if (hubConfig.kv) { + const kv = new sst.cloudflare.Kv(BINDINGS.KV); + resources.push(kv); + wranglerBuilder.addKV(kv, BINDINGS.KV); + } + + if (hubConfig.cache) { + const cache = new sst.cloudflare.Kv(BINDINGS.CACHE); + resources.push(cache); + wranglerBuilder.addKV(cache, BINDINGS.CACHE); + } + + if (hubConfig.blob) { + const blob = new sst.cloudflare.Bucket(BINDINGS.BLOB); + resources.push(blob); + wranglerBuilder.addBucket(blob); + } + + if (hubConfig.vectorize) { + for (const [name, config] of Object.entries(hubConfig.vectorize)) { + const indexName = `${$app.name}-${$app.stage}-${name}`; + const index = new command.local.Command(`Vector${indexName}`, { + dir: projectPath, + create: `${packageManagerX} wrangler vectorize create ${indexName} --dimensions=${config.dimensions} --metric=${config.metric}`, + delete: `${packageManagerX} wrangler vectorize delete ${indexName} --force`, + }); + resources.push(index); + for (const [propertyName, type] of Object.entries( + config.metadataIndexes || {}, + )) { + new command.local.Command( + `MetadataIndex${indexName}${propertyName}`, + { + dir: projectPath, + create: `${packageManagerX} wrangler vectorize create-metadata-index ${indexName} --property-name=${propertyName} --type=${type}`, + delete: `${packageManagerX} wrangler vectorize delete-metadata-index ${indexName} --property-name=${propertyName}`, + }, + { + dependsOn: [index], + }, + ); + } + wranglerBuilder.addVectorize( + name, + index.stdout.apply(() => indexName), + ); + } + } + + await wranglerBuilder.transform(transformWrangler); + const wrangler = wranglerBuilder.build(); + + $resolve(wrangler).apply((wrangler) => { + const stateDir = path.resolve(`.nuxflare/state/${$app.stage}/${name}`); + const wranglerConfigPath = path.resolve(stateDir, "wrangler.json"); + + if (!existsSync(stateDir)) { + mkdirSync(stateDir, { recursive: true }); + } + + writeFileSync(wranglerConfigPath, JSON.stringify(wrangler, null, 2)); + + const build = builder(name, { + dir: projectPath, + env: { ...extraVars }, + packageManager, + }); + + const deploy = new command.local.Command( + `${name}WorkerVersion`, + { + dir: projectPath, + create: `${packageManagerX} wrangler deploy --config ${wranglerConfigPath}`, + triggers: [new Date().toString()], + logging: command.local.Logging.Stderr, + }, + { + dependsOn: [build, ...resources], + }, + ); + + $resolve([deploy.urn, nuxtHubSecret.result]).apply(async ([_, secret]) => { + let projectUrl; + if (domain) { + projectUrl = `https://${domain}`; + } else { + const apiToken = process.env.CLOUDFLARE_API_TOKEN; + if (!apiToken) { + console.error('CLOUDFLARE_API_TOKEN environment variable is required'); + return; + } + // First, get the account ID if not set + let accountId = process.env.CLOUDFLARE_DEFAULT_ACCOUNT_ID || process.env.CLOUDFLARE_ACCOUNT_ID; + if (!accountId) { + const accountResponse = await fetch('https://api.cloudflare.com/client/v4/accounts', { + headers: { + 'Authorization': `Bearer ${apiToken}`, + 'Content-Type': 'application/json' + } + }); + const accountData = await accountResponse.json(); + if (!accountData.success || !accountData.result?.[0]?.id) { + console.error('Failed to fetch Cloudflare account ID'); + return; + } + accountId = accountData.result[0].id; + } + // Then proceed with getting the workers subdomain + const response = await fetch(`https://api.cloudflare.com/client/v4/accounts/${accountId}/workers/subdomain`, { + headers: { + 'Authorization': `Bearer ${apiToken}`, + 'Content-Type': 'application/json' + } + }); + const data = await response.json(); + if (data.success) { + const workersDomain = data.result.subdomain; + projectUrl = `https://${wrangler.name}.${workersDomain}.workers.dev`; + } else { + console.error('Failed to fetch workers subdomain'); + projectUrl = ''; + } + } + const stateFile = path.resolve(stateDir, 'state.json'); + const stateData = { + projectUrl, + nuxtHubSecret: secret, + }; + writeFileSync(stateFile, JSON.stringify(stateData, null, 2)); + }); + + new command.local.Command( + `${name}Worker`, + { + dir: projectPath, + delete: `${packageManagerX} wrangler delete --name ${wrangler.name}`, + }, + { + dependsOn: [deploy], + }, + ); + + if (databaseConfig.name) { + $resolve([deploy.urn, databaseConfig.name]).apply(([_, name]) => { + new command.local.Command( + `${name}Migrations`, + { + dir: projectPath, + create: `${existsSync(migrationsPath) + ? `${packageManagerX} wrangler --config ${wranglerConfigPath} d1 migrations apply ${name} --remote` + : 'echo "Migrations directory not found, skipping."' + }`, + triggers: [new Date().toString()], + }, + { dependsOn: [deploy] }, + ); + }); + } + }); +} + +export default Nuxt; diff --git a/src/init/sst/nuxflare/semaphore.ts b/src/init/sst/nuxflare/semaphore.ts new file mode 100644 index 0000000..5e3a557 --- /dev/null +++ b/src/init/sst/nuxflare/semaphore.ts @@ -0,0 +1,29 @@ +export class Semaphore { + private current: number; + private queue: (() => void)[]; + + constructor(private max: number) { + this.current = 0; + this.queue = []; + } + + public async acquire(): Promise { + if (this.current < this.max) { + this.current++; + return Promise.resolve(); + } + + return new Promise((resolve) => { + this.queue.push(resolve); + }); + } + + public release(): void { + if (this.queue.length > 0) { + const next = this.queue.shift(); + next?.(); + return; + } + this.current--; + } +} diff --git a/src/init/sst/sst.config.ts b/src/init/sst/sst.config.ts index da798e9..30272f8 100644 --- a/src/init/sst/sst.config.ts +++ b/src/init/sst/sst.config.ts @@ -1,5 +1,5 @@ /// -import Nuxt from "./.nuxflare/utils/nuxt"; +import Nuxt from "./nuxflare/nuxt"; const prodDomain = "__PROD_DOMAIN__"; const devDomain = "__DEV_DOMAIN__"; From 5ca23d221952129eefee529cd6cb0b10d05105e6 Mon Sep 17 00:00:00 2001 From: Tanay Karnik Date: Thu, 6 Mar 2025 19:33:50 +0530 Subject: [PATCH 12/13] again --- .github/workflows/nuxflare-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nuxflare-deploy.yml b/.github/workflows/nuxflare-deploy.yml index 697b651..d5e3157 100644 --- a/.github/workflows/nuxflare-deploy.yml +++ b/.github/workflows/nuxflare-deploy.yml @@ -69,4 +69,4 @@ jobs: fi echo "Running: ${DEPLOY_CMD}" - npx ${DEPLOY_CMD} + ../dist/index.js ${DEPLOY_CMD} From e49f1a8e056f7f397c9bd4e7e3830d17a0631e26 Mon Sep 17 00:00:00 2001 From: Tanay Karnik Date: Thu, 6 Mar 2025 19:45:28 +0530 Subject: [PATCH 13/13] asdf --- src/utils/sst.ts | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/utils/sst.ts b/src/utils/sst.ts index 83624e9..eac1f9a 100644 --- a/src/utils/sst.ts +++ b/src/utils/sst.ts @@ -24,9 +24,33 @@ export async function executeSST( const stdio = options.stdio || "pipe"; const cwd = options.cwd || process.cwd(); const env = options.env ? { ...process.env, ...options.env } : process.env; - return execSync(`${command} ${args.join(" ")}`, { stdio, cwd, env }) - .toString() - .trim(); + + if (stdio === "inherit") { + return new Promise((resolve, reject) => { + const childProcess = spawn(command, args, { + stdio, + cwd, + env, + shell: true, + }); + + childProcess.on("close", (code) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`Process exited with code ${code}`)); + } + }); + + childProcess.on("error", (err) => { + reject(err); + }); + }); + } else { + return execSync(`${command} ${args.join(" ")}`, { stdio, cwd, env }) + .toString() + .trim(); + } } catch (error) { if (error instanceof Error) { throw new Error(`Failed to execute SST command: ${error.message}`);