diff --git a/.changeset/fresh-comics-carry.md b/.changeset/fresh-comics-carry.md new file mode 100644 index 000000000000..76030e7abf66 --- /dev/null +++ b/.changeset/fresh-comics-carry.md @@ -0,0 +1,5 @@ +--- +"wrangler": patch +--- + +fix: Use specific error code to signal a wrangler.toml file not being found in build-env diff --git a/.changeset/little-baboons-reflect.md b/.changeset/little-baboons-reflect.md new file mode 100644 index 000000000000..cfa902a68a61 --- /dev/null +++ b/.changeset/little-baboons-reflect.md @@ -0,0 +1,5 @@ +--- +"wrangler": minor +--- + +feat: Add `wrangler pages download config` diff --git a/packages/wrangler/src/__tests__/pages/pages-build-env.test.ts b/packages/wrangler/src/__tests__/pages/pages-build-env.test.ts index c94f02963147..8a2a9bd03cf3 100644 --- a/packages/wrangler/src/__tests__/pages/pages-build-env.test.ts +++ b/packages/wrangler/src/__tests__/pages/pages-build-env.test.ts @@ -18,7 +18,7 @@ describe("pages-build-env", () => { pages_build_output_dir: "./dist", vars: {}, }); - await runWrangler("pages functions build-env"); + await runWrangler("pages functions build-env ."); expect(std).toMatchInlineSnapshot(` Object { "debug": "", @@ -32,7 +32,7 @@ describe("pages-build-env", () => { it("should fail with no config file", async () => { await expect( - runWrangler("pages functions build-env") + runWrangler("pages functions build-env .") ).rejects.toThrowErrorMatchingInlineSnapshot( `"No Pages config file found"` ); @@ -65,7 +65,7 @@ describe("pages-build-env", () => { }, }, }); - await runWrangler("pages functions build-env"); + await runWrangler("pages functions build-env ."); expect(std).toMatchInlineSnapshot(` Object { "debug": "", @@ -104,7 +104,7 @@ describe("pages-build-env", () => { }, }, }); - await runWrangler("pages functions build-env"); + await runWrangler("pages functions build-env ."); expect(std).toMatchInlineSnapshot(` Object { "debug": "", @@ -144,7 +144,7 @@ describe("pages-build-env", () => { }, }, }); - await runWrangler("pages functions build-env"); + await runWrangler("pages functions build-env ."); expect(std).toMatchInlineSnapshot(` Object { "debug": "", diff --git a/packages/wrangler/src/__tests__/pages/pages-download-config.test.ts b/packages/wrangler/src/__tests__/pages/pages-download-config.test.ts new file mode 100644 index 000000000000..1eab045c4af2 --- /dev/null +++ b/packages/wrangler/src/__tests__/pages/pages-download-config.test.ts @@ -0,0 +1,450 @@ +/* eslint-disable turbo/no-undeclared-env-vars */ +import { randomUUID } from "crypto"; +import { readFile } from "fs/promises"; +import { rest } from "msw"; +import { mockAccountId, mockApiToken } from "../helpers/mock-account-id"; +import { mockConsoleMethods } from "../helpers/mock-console"; +import { msw } from "../helpers/msw"; +import { runInTempDir } from "../helpers/run-in-tmp"; +import { runWrangler } from "../helpers/run-wrangler"; + +function mockSupportingDashRequests( + expectedAccountId: string, + expectedProjectName: string +) { + msw.use( + rest.get( + `*/accounts/:accountId/pages/projects/NOT_REAL`, + (req, res, ctx) => { + expect(req.params.accountId).toEqual(expectedAccountId); + + return res.once( + ctx.status(404), + ctx.json({ + success: false, + errors: [ + { + code: 8000007, + message: + "Project not found. The specified project name does not match any of your existing projects.", + }, + ], + result: null, + }) + ); + } + ), + rest.get( + `*/accounts/:accountId/pages/projects/:projectName`, + (req, res, ctx) => { + expect(req.params.accountId).toEqual(expectedAccountId); + expect(req.params.projectName).toEqual(expectedProjectName); + + return res.once( + ctx.status(200), + ctx.json({ + success: true, + errors: [], + result: { + id: randomUUID(), + name: expectedProjectName, + subdomain: `${expectedProjectName}.pages.dev`, + domains: [`${expectedProjectName}.pages.dev`], + source: { + type: "github", + config: { + owner: "workers-devprod", + repo_name: expectedProjectName, + production_branch: "main", + pr_comments_enabled: true, + deployments_enabled: true, + production_deployments_enabled: true, + preview_deployment_setting: "all", + preview_branch_includes: ["*"], + preview_branch_excludes: [], + path_includes: ["*"], + path_excludes: [], + }, + }, + build_config: { + build_command: 'node -e "console.log(process.env)"', + destination_dir: "dist-test", + root_dir: "", + web_analytics_tag: null, + web_analytics_token: null, + }, + deployment_configs: { + preview: { + env_vars: { + TEST_JSON_PREVIEW: { + type: "plain_text", + value: '{\njson: "value"\n}', + }, + TEST_PLAINTEXT_PREVIEW: { + type: "plain_text", + value: "PLAINTEXT", + }, + TEST_SECRET_PREVIEW: { + type: "secret_text", + value: "", + }, + TEST_SECRET_2_PREVIEW: { + type: "secret_text", + value: "", + }, + }, + kv_namespaces: { + KV_PREVIEW: { + namespace_id: "kv-id", + }, + KV_PREVIEW2: { + namespace_id: "kv-id", + }, + }, + durable_object_namespaces: { + DO_PREVIEW: { + namespace_id: "do-id", + }, + DO_PREVIEW2: { + namespace_id: "do-id", + }, + DO_PREVIEW3: { + class_name: "do-class", + service: "do-s", + environment: "do-e", + }, + }, + d1_databases: { + D1_PREVIEW: { + id: "d1-id", + }, + D1_PREVIEW2: { + id: "d1-id", + }, + }, + r2_buckets: { + R2_PREVIEW: { + name: "r2-name", + }, + R2_PREVIEW2: { + name: "r2-name", + }, + }, + services: { + SERVICE_PREVIEW: { + service: "service", + environment: "production", + }, + SERVICE_PREVIEW2: { + service: "service", + environment: "production", + }, + }, + queue_producers: { + QUEUE_PREVIEW: { + name: "q-id", + }, + QUEUE_PREVIEW2: { + name: "q-id", + }, + }, + analytics_engine_datasets: { + AE_PREVIEW: { + dataset: "data", + }, + AE_PREVIEW2: { + dataset: "data", + }, + }, + ai_bindings: { + AI_PREVIEW: {}, + }, + fail_open: true, + always_use_latest_compatibility_date: false, + compatibility_date: "2023-02-14", + compatibility_flags: [], + build_image_major_version: 2, + usage_model: "standard", + limits: { + cpu_ms: 500, + }, + placement: { + mode: "normal", + }, + }, + production: { + env_vars: { + TEST_JSON: { + type: "plain_text", + value: '{\njson: "value"\n}', + }, + TEST_PLAINTEXT: { + type: "plain_text", + value: "PLAINTEXT", + }, + TEST_SECRET: { + type: "secret_text", + value: "", + }, + TEST_SECRET_2: { + type: "secret_text", + value: "", + }, + }, + kv_namespaces: { + KV: { + namespace_id: "kv-id", + }, + }, + durable_object_namespaces: { + DO: { + namespace_id: "do-id", + }, + }, + d1_databases: { + D1: { + id: "d1-id", + }, + }, + r2_buckets: { + R2: { + name: "r2-name", + }, + }, + services: { + SERVICE: { + service: "service", + environment: "production", + }, + }, + queue_producers: { + QUEUE: { + name: "q-id", + }, + }, + analytics_engine_datasets: { + AE: { + dataset: "data", + }, + }, + ai_bindings: { + AI: {}, + }, + fail_open: true, + always_use_latest_compatibility_date: false, + compatibility_date: "2024-02-14", + compatibility_flags: [], + build_image_major_version: 2, + usage_model: "standard", + limits: { + cpu_ms: 50, + }, + placement: { + mode: "smart", + }, + }, + }, + }, + }) + ); + } + ), + rest.get( + `*/accounts/:accountId/workers/durable_objects/namespaces/:doId`, + (req, res, ctx) => { + expect(req.params.accountId).toEqual(expectedAccountId); + + return res( + ctx.status(200), + ctx.json({ + success: true, + errors: [], + result: { + script: `some-script-${req.params.doId}`, + class: `some-class-${req.params.doId}`, + environment: `some-environment-${req.params.doId}`, + }, + }) + ); + } + ) + ); +} +describe("pages-download-config", () => { + const std = mockConsoleMethods(); + runInTempDir(); + + mockApiToken(); + const MOCK_ACCOUNT_ID = "MOCK_ACCOUNT_ID"; + const MOCK_PROJECT_NAME = "MOCK_PROJECT_NAME"; + mockAccountId({ accountId: MOCK_ACCOUNT_ID }); + + beforeEach(() => { + mockSupportingDashRequests(MOCK_ACCOUNT_ID, MOCK_PROJECT_NAME); + }); + + it("should download full config correctly", async () => { + await runWrangler(`pages download config ${MOCK_PROJECT_NAME}`); + + await expect( + // Drop the Wrangler generation header + (await readFile("wrangler.toml", "utf8")).split("\n").slice(1).join("\n") + ).toMatchInlineSnapshot(` + "name = \\"MOCK_PROJECT_NAME\\" + pages_build_output_dir = \\"dist-test\\" + compatibility_date = \\"2023-02-14\\" + + [vars] + TEST_JSON_PREVIEW = \\"\\"\\" + { + json: \\"value\\" + }\\"\\"\\" + TEST_PLAINTEXT_PREVIEW = \\"PLAINTEXT\\" + + [[kv_namespaces]] + id = \\"kv-id\\" + binding = \\"KV_PREVIEW\\" + + [[kv_namespaces]] + id = \\"kv-id\\" + binding = \\"KV_PREVIEW2\\" + + [[durable_objects.bindings]] + name = \\"DO_PREVIEW\\" + class_name = \\"some-class-do-id\\" + script_name = \\"some-script-do-id\\" + environment = \\"some-environment-do-id\\" + + [[durable_objects.bindings]] + name = \\"DO_PREVIEW2\\" + class_name = \\"some-class-do-id\\" + script_name = \\"some-script-do-id\\" + environment = \\"some-environment-do-id\\" + + [[durable_objects.bindings]] + name = \\"DO_PREVIEW3\\" + class_name = \\"do-class\\" + script_name = \\"do-s\\" + environment = \\"do-e\\" + + [[d1_databases]] + database_id = \\"d1-id\\" + binding = \\"D1_PREVIEW\\" + database_name = \\"D1_PREVIEW\\" + + [[d1_databases]] + database_id = \\"d1-id\\" + binding = \\"D1_PREVIEW2\\" + database_name = \\"D1_PREVIEW2\\" + + [[r2_buckets]] + bucket_name = \\"r2-name\\" + binding = \\"R2_PREVIEW\\" + + [[r2_buckets]] + bucket_name = \\"r2-name\\" + binding = \\"R2_PREVIEW2\\" + + [[services]] + binding = \\"SERVICE_PREVIEW\\" + service = \\"service\\" + environment = \\"production\\" + + [[services]] + binding = \\"SERVICE_PREVIEW2\\" + service = \\"service\\" + environment = \\"production\\" + + [[queues.producers]] + binding = \\"QUEUE_PREVIEW\\" + queue = \\"q-id\\" + + [[queues.producers]] + binding = \\"QUEUE_PREVIEW2\\" + queue = \\"q-id\\" + + [[analytics_engine_datasets]] + binding = \\"AE_PREVIEW\\" + dataset = \\"data\\" + + [[analytics_engine_datasets]] + binding = \\"AE_PREVIEW2\\" + dataset = \\"data\\" + + [ai] + binding = \\"AI_PREVIEW\\" + + [env.production] + compatibility_date = \\"2024-02-14\\" + + [env.production.vars] + TEST_JSON = \\"\\"\\" + { + json: \\"value\\" + }\\"\\"\\" + TEST_PLAINTEXT = \\"PLAINTEXT\\" + + [[env.production.kv_namespaces]] + id = \\"kv-id\\" + binding = \\"KV\\" + + [[env.production.durable_objects.bindings]] + name = \\"DO\\" + class_name = \\"some-class-do-id\\" + script_name = \\"some-script-do-id\\" + environment = \\"some-environment-do-id\\" + + [[env.production.d1_databases]] + database_id = \\"d1-id\\" + binding = \\"D1\\" + database_name = \\"D1\\" + + [[env.production.r2_buckets]] + bucket_name = \\"r2-name\\" + binding = \\"R2\\" + + [[env.production.services]] + binding = \\"SERVICE\\" + service = \\"service\\" + environment = \\"production\\" + + [[env.production.queues.producers]] + binding = \\"QUEUE\\" + queue = \\"q-id\\" + + [[env.production.analytics_engine_datasets]] + binding = \\"AE\\" + dataset = \\"data\\" + + [env.production.ai] + binding = \\"AI\\" + " + `); + }); + it("should fail if not given a project name", async () => { + await expect( + runWrangler(`pages download config`) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Must specify a project name."` + ); + }); + it("should fail if project does not exist", async () => { + await expect( + runWrangler(`pages download config NOT_REAL`) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"A request to the Cloudflare API (/accounts/MOCK_ACCOUNT_ID/pages/projects/NOT_REAL) failed."` + ); + expect(std.out).toMatchInlineSnapshot(` + " + X [ERROR] A request to the Cloudflare API (/accounts/MOCK_ACCOUNT_ID/pages/projects/NOT_REAL) failed. + + Project not found. The specified project name does not match any of your existing projects. [code: + 8000007] + + If you think this is a bug, please open an issue at: + https://github.com/cloudflare/workers-sdk/issues/new/choose + + " + `); + }); +}); diff --git a/packages/wrangler/src/__tests__/pages/pages.test.ts b/packages/wrangler/src/__tests__/pages/pages.test.ts index d8e0f22e6f92..26eb06b99b47 100644 --- a/packages/wrangler/src/__tests__/pages/pages.test.ts +++ b/packages/wrangler/src/__tests__/pages/pages.test.ts @@ -27,6 +27,7 @@ describe("pages", () => { wrangler pages project ⚡️ Interact with your Pages projects wrangler pages deployment 🚀 Interact with the deployments of a project wrangler pages deploy [directory] 🆙 Deploy a directory of static assets as a Pages deployment [aliases: publish] + wrangler pages download ⚡️ Download settings from your project Flags: -j, --experimental-json-config Experimental: Support wrangler.json [boolean] diff --git a/packages/wrangler/src/metrics/send-event.ts b/packages/wrangler/src/metrics/send-event.ts index 60228322c2e5..861579f97c50 100644 --- a/packages/wrangler/src/metrics/send-event.ts +++ b/packages/wrangler/src/metrics/send-event.ts @@ -71,7 +71,8 @@ export type EventNames = | "list worker versions" | "view versioned deployment" | "view latest versioned deployment" - | "list versioned deployments"; + | "list versioned deployments" + | "download pages config"; /** * Send a metrics event, with no extra properties, to Cloudflare, if usage tracking is enabled. diff --git a/packages/wrangler/src/pages/build-env.ts b/packages/wrangler/src/pages/build-env.ts index c359d5408c06..754d1d9a7042 100644 --- a/packages/wrangler/src/pages/build-env.ts +++ b/packages/wrangler/src/pages/build-env.ts @@ -1,6 +1,13 @@ +import { existsSync } from "node:fs"; +import path from "node:path"; import { readConfig } from "../config"; +import { isPagesConfig } from "../config/validation"; import { FatalError } from "../errors"; import { logger } from "../logger"; +import { + EXIT_CODE_INVALID_PAGES_CONFIG, + EXIT_CODE_NO_CONFIG_FOUND, +} from "./errors"; import type { CommonYargsArgv, StrictYargsOptionsToInterface, @@ -9,17 +16,35 @@ import type { export type PagesBuildEnvArgs = StrictYargsOptionsToInterface; export function Options(yargs: CommonYargsArgv) { - return yargs; + return yargs.positional("projectDir", { + type: "string", + description: "The location of the Pages project", + }); } export const Handler = async (args: PagesBuildEnvArgs) => { - const config = readConfig(undefined, { + if (!args.projectDir) { + throw new FatalError("No Pages project location specified"); + } + const configPath = path.resolve(args.projectDir, "wrangler.toml"); + // Fail early if the config file doesn't exist + if (!existsSync(configPath)) { + throw new FatalError( + "No Pages config file found", + EXIT_CODE_NO_CONFIG_FOUND + ); + } + + const config = readConfig(path.resolve(args.projectDir, "wrangler.toml"), { ...args, // eslint-disable-next-line turbo/no-undeclared-env-vars env: process.env.PAGES_ENVIRONMENT, }); - if (!config.pages_build_output_dir) { - throw new FatalError("No Pages config file found"); + if (!isPagesConfig(config)) { + throw new FatalError( + "Your wrangler.toml is not a valid Pages config file", + EXIT_CODE_INVALID_PAGES_CONFIG + ); } // Ensure JSON variables are not included diff --git a/packages/wrangler/src/pages/build.ts b/packages/wrangler/src/pages/build.ts index ac22ee691e15..f691a48cd7b6 100644 --- a/packages/wrangler/src/pages/build.ts +++ b/packages/wrangler/src/pages/build.ts @@ -45,6 +45,10 @@ export function Options(yargs: CommonYargsArgv) { type: "string", description: "The location for the output config file", }, + "build-metadata-path": { + type: "string", + description: "The location for the build metadata file", + }, "output-routes-path": { type: "string", description: "The location for the output _routes.json file", diff --git a/packages/wrangler/src/pages/download-config.ts b/packages/wrangler/src/pages/download-config.ts new file mode 100644 index 000000000000..51f880b41919 --- /dev/null +++ b/packages/wrangler/src/pages/download-config.ts @@ -0,0 +1,221 @@ +import { writeFile } from "node:fs/promises"; +import TOML from "@iarna/toml"; +import chalk from "chalk"; +import { fetchResult } from "../cfetch"; +import { getConfigCache } from "../config-cache"; +import { FatalError } from "../errors"; +import { logger } from "../logger"; +import * as metrics from "../metrics"; +import { printWranglerBanner } from "../update-check"; +import { requireAuth } from "../user"; +import { PAGES_CONFIG_CACHE_FILENAME } from "./constants"; +import type { RawEnvironment } from "../config"; +import type { + CommonYargsArgv, + StrictYargsOptionsToInterface, +} from "../yargs-types"; +import type { PagesConfigCache } from "./types"; +import type { Project } from "@cloudflare/types"; + +// TODO: fix the Project definition +type DeploymentConfig = Project["deployment_configs"]["production"]; +interface PagesDeploymentConfig extends DeploymentConfig { + services: Record< + string, + { + service: string; + environment?: string; + } + >; + queue_producers: Record< + string, + { + name: string; + } + >; + analytics_engine_datasets: Record< + string, + { + dataset: string; + } + >; + durable_object_namespaces: Record< + string, + { + namespace_id: string; + class_name: string; + service: string; + environment: string; + } + >; + ai_bindings: Record>; +} + +interface PagesProject extends Project { + deployment_configs: { + production: PagesDeploymentConfig; + preview: PagesDeploymentConfig; + }; +} + +async function toEnvironment( + project: PagesDeploymentConfig, + accountId: string +): Promise { + const configObj = {} as RawEnvironment; + configObj.compatibility_date = + project.compatibility_date ?? new Date().toISOString().substring(0, 10); + if (project.compatibility_flags?.length) + configObj.compatibility_flags = project.compatibility_flags; + + for (const [name, envVar] of Object.entries(project.env_vars ?? {})) { + if (envVar?.value && envVar?.type == "plain_text") { + configObj.vars ??= {}; + configObj.vars[name] = envVar?.value; + } + } + + for (const [name, namespace] of Object.entries(project.kv_namespaces ?? {})) { + configObj.kv_namespaces ??= []; + configObj.kv_namespaces.push({ id: namespace.namespace_id, binding: name }); + } + + for (const [name, ns] of Object.entries( + project.durable_object_namespaces ?? {} + )) { + configObj.durable_objects ??= { bindings: [] }; + if (ns.class_name && ns.class_name !== "") { + configObj.durable_objects.bindings.push({ + name: name, + class_name: ns.class_name, + script_name: ns.service, + environment: ns.environment, + }); + } else { + const namespace = await fetchResult<{ + script: string; + class: string; + environment?: string; + }>( + `/accounts/${accountId}/workers/durable_objects/namespaces/${ns.namespace_id}` + ); + configObj.durable_objects.bindings.push({ + name: name, + class_name: namespace.class, + script_name: namespace.script, + environment: namespace.environment, + }); + } + } + + for (const [name, namespace] of Object.entries(project.d1_databases ?? {})) { + configObj.d1_databases ??= []; + configObj.d1_databases.push({ + database_id: namespace.id, + binding: name, + database_name: name, + }); + } + + for (const [name, bucket] of Object.entries(project.r2_buckets ?? {})) { + configObj.r2_buckets ??= []; + configObj.r2_buckets.push({ + bucket_name: bucket.name, + binding: name, + }); + } + + for (const [name, { service, environment }] of Object.entries( + project.services ?? {} + )) { + configObj.services ??= []; + configObj.services.push({ + binding: name, + service, + environment, + }); + } + + for (const [name, queue] of Object.entries(project.queue_producers ?? {})) { + configObj.queues ??= { producers: [] }; + configObj.queues?.producers?.push({ + binding: name, + queue: queue.name, + }); + } + + for (const [name, { dataset }] of Object.entries( + project.analytics_engine_datasets ?? {} + )) { + configObj.analytics_engine_datasets ??= []; + configObj.analytics_engine_datasets.push({ + binding: name, + dataset, + }); + } + for (const [name] of Object.entries(project.ai_bindings ?? {})) { + configObj.ai = { binding: name }; + } + return configObj; +} +async function writeWranglerToml(toml: RawEnvironment) { + // Pages does not support custom wrangler.toml locations, so always write to ./wrangler.toml + await writeFile( + "wrangler.toml", + [ + `# Generated by Wrangler on ${new Date()}`, + TOML.stringify(toml as TOML.JsonMap), + ].join("\n") + ); +} + +async function downloadProject(accountId: string, projectName: string) { + const project = await fetchResult( + `/accounts/${accountId}/pages/projects/${projectName}` + ); + logger.debug(JSON.stringify(project, null, 2)); + + return { + name: project.name, + pages_build_output_dir: project.build_config.destination_dir, + ...(await toEnvironment(project.deployment_configs.preview, accountId)), + env: { + production: await toEnvironment( + project.deployment_configs.production, + accountId + ), + }, + }; +} + +type DownloadConfigArgs = StrictYargsOptionsToInterface; + +export function Options(yargs: CommonYargsArgv) { + return yargs.positional("projectName", { + type: "string", + description: "The Pages project to download", + }); +} + +export const Handler = async ({ projectName }: DownloadConfigArgs) => { + void metrics.sendMetricsEvent("download pages config"); + await printWranglerBanner(); + + const projectConfig = getConfigCache( + PAGES_CONFIG_CACHE_FILENAME + ); + const accountId = await requireAuth(projectConfig); + + projectName ??= projectConfig.project_name; + + if (!projectName) { + throw new FatalError("Must specify a project name.", 1); + } + const config = await downloadProject(accountId, projectName); + await writeWranglerToml(config); + logger.info( + chalk.green( + "Success! Your project settings have been downloaded to wrangler.toml" + ) + ); +}; diff --git a/packages/wrangler/src/pages/errors.ts b/packages/wrangler/src/pages/errors.ts index c64d97477932..e179e2f226fe 100644 --- a/packages/wrangler/src/pages/errors.ts +++ b/packages/wrangler/src/pages/errors.ts @@ -20,6 +20,9 @@ export enum ApiErrorCodes { export const EXIT_CODE_FUNCTIONS_NO_ROUTES_ERROR = 156; export const EXIT_CODE_FUNCTIONS_NOTHING_TO_BUILD_ERROR = 157; +export const EXIT_CODE_NO_CONFIG_FOUND = 158; +export const EXIT_CODE_INVALID_PAGES_CONFIG = 159; + /** * Pages error when building a script from the functions directory fails */ diff --git a/packages/wrangler/src/pages/index.ts b/packages/wrangler/src/pages/index.ts index b4f70ce175b1..af8ad5fc2e9a 100644 --- a/packages/wrangler/src/pages/index.ts +++ b/packages/wrangler/src/pages/index.ts @@ -6,6 +6,7 @@ import * as Deploy from "./deploy"; import * as DeploymentTails from "./deployment-tails"; import * as Deployments from "./deployments"; import * as Dev from "./dev"; +import * as DownloadConfig from "./download-config"; import * as Functions from "./functions"; import * as Projects from "./projects"; import * as Upload from "./upload"; @@ -44,7 +45,7 @@ export function pages(yargs: CommonYargsArgv) { Build.Handler ) .command( - "build-env", + "build-env [projectDir]", "Render a list of environment variables from the config file", BuildEnv.Options, BuildEnv.Handler @@ -115,5 +116,13 @@ export function pages(yargs: CommonYargsArgv) { Deploy.Options, Deploy.Handler ) + .command("download", "⚡️ Download settings from your project", (args) => + args.command( + "config [projectName]", + "Experimental: Download your Pages project config as a wrangler.toml file", + DownloadConfig.Options, + DownloadConfig.Handler + ) + ) ); }