veryfront 0.1.1062 → 0.1.1063

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"demo.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/demo/demo.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAqDH,KAAK,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,GAAG,OAAO,CAAC;AA6Y9D,MAAM,WAAW,WAAW;IAC1B,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,8CAA8C;IAC9C,WAAW,CAAC,EAAE,UAAU,CAAC;CAC1B;AA+DD,wBAAsB,WAAW,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4G1E"}
1
+ {"version":3,"file":"demo.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/demo/demo.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAqDH,KAAK,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,GAAG,OAAO,CAAC;AA6Y9D,MAAM,WAAW,WAAW;IAC1B,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,8CAA8C;IAC9C,WAAW,CAAC,EAAE,UAAU,CAAC;CAC1B;AA+DD,wBAAsB,WAAW,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmH1E"}
@@ -482,7 +482,8 @@ export async function demoCommand(options = {}) {
482
482
  console.log();
483
483
  console.log(` ${dim("1.")} Edit your app in ${brand(`${projectName}/`)}`);
484
484
  console.log(` ${dim("2.")} Run ${brand("veryfront dev")} to start developing`);
485
- console.log(` ${dim("3.")} Run ${brand("veryfront deploy")} to publish changes`);
485
+ console.log(` ${dim("3.")} Run ${brand("veryfront push --branch main")} to upload changes`);
486
+ console.log(` ${dim("4.")} Run ${brand("veryfront deploy --branch main --env production")} to create a release and deploy`);
486
487
  console.log();
487
488
  console.log(` ${dim("Learn more at https://veryfront.com/docs")}`);
488
489
  console.log();
@@ -40,12 +40,12 @@ export const DEMO_STEPS = [
40
40
  },
41
41
  {
42
42
  id: "deploy",
43
- title: "Deploy to Production",
43
+ title: "Create a Release and Deploy",
44
44
  description: [
45
- "Deploy your app to Veryfront's global edge network with a single command.",
46
- "No build step required - we handle that for you.",
45
+ "The demo pushed your source to main when it created the project.",
46
+ "Now create a release from that source and deploy it to production.",
47
47
  ],
48
- command: "veryfront deploy",
48
+ command: "veryfront deploy --branch main --env production",
49
49
  hasAction: true,
50
50
  },
51
51
  {
@@ -1 +1 @@
1
- {"version":3,"file":"command-help.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/deploy/command-help.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,eAAO,MAAM,UAAU,EAAE,WAwCxB,CAAC"}
1
+ {"version":3,"file":"command-help.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/deploy/command-help.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,eAAO,MAAM,UAAU,EAAE,WA6CxB,CAAC"}
@@ -18,18 +18,23 @@ export const deployHelp = {
18
18
  },
19
19
  {
20
20
  flag: "-f, --force",
21
- description: "Deploy without confirmation",
21
+ description: "Skip confirmation for compatibility (prefer global --yes)",
22
22
  },
23
23
  {
24
24
  flag: "--dry-run",
25
25
  description: "Preview without executing",
26
26
  },
27
+ {
28
+ flag: "-q, --quiet",
29
+ description: "Suppress progress and summary output",
30
+ },
27
31
  ],
28
32
  examples: [
29
33
  "veryfront deploy",
30
34
  "veryfront deploy --env staging",
31
35
  "veryfront deploy --branch feature-x --env preview",
32
36
  "veryfront deploy --release-name v1.2.0",
37
+ "veryfront deploy --branch main --env production --yes",
33
38
  "veryfront deploy --dry-run",
34
39
  ],
35
40
  notes: [
@@ -1 +1 @@
1
- {"version":3,"file":"init-command.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/init/init-command.ts"],"names":[],"mappings":"AAAA;;;iCAGiC;AAWjC,OAAO,KAAK,EAAE,WAAW,EAA6B,MAAM,YAAY,CAAC;AA6PzE;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA8arE"}
1
+ {"version":3,"file":"init-command.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/init/init-command.ts"],"names":[],"mappings":"AAAA;;;iCAGiC;AAWjC,OAAO,KAAK,EAAE,WAAW,EAA6B,MAAM,YAAY,CAAC;AA6PzE;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA+arE"}
@@ -463,14 +463,15 @@ export async function initCommand(options) {
463
463
  const { writeProjectSlug } = await import("../../shared/config.js");
464
464
  const { pushCommand } = await import("../push/index.js");
465
465
  const { deployCommand } = await import("../deploy/index.js");
466
+ const manualDeployHint = `Run ${brand("veryfront push --branch main")}, then ${brand("veryfront deploy --branch main --env production")} to deploy later.`;
466
467
  const authResult = await ensureAuthenticated();
467
468
  if (!authResult) {
468
- log(`\n Authentication required for --deploy. Run ${brand("veryfront push")} to deploy later.`);
469
+ log(`\n Authentication required for --deploy. ${manualDeployHint}`);
469
470
  }
470
471
  else {
471
472
  const token = await readToken();
472
473
  if (!token) {
473
- log(`\n Could not read auth token. Run ${brand("veryfront push")} to deploy later.`);
474
+ log(`\n Could not read auth token. ${manualDeployHint}`);
474
475
  }
475
476
  else {
476
477
  const slug = `${projectName ?? "my-app"}-${randomSuffix()}`;
@@ -499,7 +500,7 @@ export async function initCommand(options) {
499
500
  catch (error) {
500
501
  const message = error instanceof Error ? error.message : String(error);
501
502
  log(`\n Deploy failed: ${message}`);
502
- log(` Your project was created locally. Run ${brand("veryfront push")} to deploy later.`);
503
+ log(` Your project was created locally. ${manualDeployHint}`);
503
504
  }
504
505
  }
505
506
  }
@@ -531,7 +532,7 @@ export async function initCommand(options) {
531
532
  successContent.push("", `${green("Live:")} https://${deployedSlug}.production.veryfront.com`);
532
533
  }
533
534
  if (!deployedSlug) {
534
- successContent.push("", `${brand("veryfront push")} ${dim("→ share a preview")}`, `${brand("veryfront deploy")} ${dim("→ go live")}`);
535
+ successContent.push("", `${brand("veryfront push --branch main")} ${dim("→ upload source")}`, `${brand("veryfront deploy --branch main --env production")} ${dim("→ create a release and go live")}`);
535
536
  }
536
537
  if (!quiet) {
537
538
  console.log("");
@@ -1 +1 @@
1
- {"version":3,"file":"command-help.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/pull/command-help.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,eAAO,MAAM,QAAQ,EAAE,WA2DtB,CAAC"}
1
+ {"version":3,"file":"command-help.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/pull/command-help.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,eAAO,MAAM,QAAQ,EAAE,WAuEtB,CAAC"}
@@ -30,11 +30,19 @@ export const pullHelp = {
30
30
  },
31
31
  {
32
32
  flag: "-f, --force",
33
- description: "Force overwrite without confirmation",
33
+ description: "Skip confirmation for compatibility (prefer global --yes)",
34
34
  },
35
35
  {
36
36
  flag: "--dry-run",
37
- description: "Show what would be written without writing",
37
+ description: "Show what would be written or deleted without changing files",
38
+ },
39
+ {
40
+ flag: "--prune",
41
+ description: "Delete managed local files missing from the selected source",
42
+ },
43
+ {
44
+ flag: "-q, --quiet",
45
+ description: "Suppress progress and summary output",
38
46
  },
39
47
  ],
40
48
  examples: [
@@ -47,8 +55,10 @@ export const pullHelp = {
47
55
  "veryfront pull --release v1.2.0",
48
56
  "veryfront pull --projects project-a,project-b,project-c",
49
57
  "veryfront pull --projects my-app --dir ./apps",
58
+ "veryfront pull --branch studio-change --prune --dry-run",
59
+ "veryfront pull --branch studio-change --prune --yes",
50
60
  "veryfront pull --dry-run",
51
- "veryfront pull --force",
61
+ "veryfront pull --yes",
52
62
  ],
53
63
  notes: [
54
64
  "Requires VERYFRONT_API_TOKEN env var or veryfront.json config",
@@ -56,5 +66,7 @@ export const pullHelp = {
56
66
  "With --projects, each project is pulled into a subdirectory named after the slug",
57
67
  'Projects list can also be specified in veryfront.json: { "projects": ["slug1", "slug2"] }',
58
68
  "Priority order: --env > --release > --branch > main",
69
+ "--prune requires a clean Git worktree; --prune --dry-run can preview anywhere",
70
+ "--prune never removes ignored or unsupported files",
59
71
  ],
60
72
  };
@@ -20,6 +20,7 @@ export declare const getPullArgsSchema: () => import("../../../src/internal-agen
20
20
  release: import("../../../src/internal-agents/schema.js").Schema<string | undefined>;
21
21
  force: import("../../../src/internal-agents/schema.js").Schema<boolean>;
22
22
  dryRun: import("../../../src/internal-agents/schema.js").Schema<boolean>;
23
+ prune: import("../../../src/internal-agents/schema.js").Schema<boolean>;
23
24
  quiet: import("../../../src/internal-agents/schema.js").Schema<boolean>;
24
25
  }>>;
25
26
  export declare const PullArgsSchema: import("../../../src/internal-agents/schema.js").Schema<import("../../../src/extensions/schema/schema-validator.js").InferShape<{
@@ -31,6 +32,7 @@ export declare const PullArgsSchema: import("../../../src/internal-agents/schema
31
32
  release: import("../../../src/internal-agents/schema.js").Schema<string | undefined>;
32
33
  force: import("../../../src/internal-agents/schema.js").Schema<boolean>;
33
34
  dryRun: import("../../../src/internal-agents/schema.js").Schema<boolean>;
35
+ prune: import("../../../src/internal-agents/schema.js").Schema<boolean>;
34
36
  quiet: import("../../../src/internal-agents/schema.js").Schema<boolean>;
35
37
  }>>;
36
38
  export type PullArgs = InferSchema<ReturnType<typeof getPullArgsSchema>>;
@@ -46,6 +48,7 @@ export declare const parsePullArgs: (args: import("../../shared/types.js").Parse
46
48
  release: import("../../../src/internal-agents/schema.js").Schema<string | undefined>;
47
49
  force: import("../../../src/internal-agents/schema.js").Schema<boolean>;
48
50
  dryRun: import("../../../src/internal-agents/schema.js").Schema<boolean>;
51
+ prune: import("../../../src/internal-agents/schema.js").Schema<boolean>;
49
52
  quiet: import("../../../src/internal-agents/schema.js").Schema<boolean>;
50
53
  }>>;
51
54
  /**
@@ -79,10 +82,12 @@ export interface PullOptions {
79
82
  env?: string;
80
83
  /** Release version to pull from (e.g., "v1.2.0") */
81
84
  release?: string;
82
- /** Force overwrite without confirmation */
85
+ /** Skip confirmation without changing overwrite semantics */
83
86
  force?: boolean;
84
87
  /** Dry run - show what would be written without writing */
85
88
  dryRun?: boolean;
89
+ /** Delete managed local files that are absent from the selected source */
90
+ prune?: boolean;
86
91
  /** Quiet mode - suppress spinner/progress output */
87
92
  quiet?: boolean;
88
93
  }
@@ -98,6 +103,8 @@ interface ProjectFile {
98
103
  created_at: string;
99
104
  updated_at: string;
100
105
  }
106
+ /** Validate a remote file path as a canonical relative POSIX path. */
107
+ export declare function validateRemoteFilePath(filePath: string): string;
101
108
  /**
102
109
  * Build the files list URL based on pull source
103
110
  */
@@ -1 +1 @@
1
- {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/pull/command.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yCAAyC,CAAC;AAM3E,OAAO,EACL,eAAe,EAIhB,MAAM,wBAAwB,CAAC;AAgBhC;;GAEG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;GAY7B,CAAC;AAEF,eAAO,MAAM,cAAc;;;;;;;;;;GAAgC,CAAC;AAE5D,MAAM,MAAM,QAAQ,GAAG,WAAW,CAAC,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC,CAAC;AAEzE;;GAEG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;GAUxB,CAAC;AAEH;;GAEG;AACH,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzC;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,gCAAgC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,0CAA0C;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,oDAAoD;IACpD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,UAAU,CAKlE;AAED,UAAU,WAAW;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAyDD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,MAAM,CAWjF;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,EAC1C,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,UAAU,GACjB,OAAO,CAAC,WAAW,EAAE,CAAC,CAoBxB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,MAAM,CAmBjG;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,EAC1C,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,UAAU,GACjB,OAAO,CAAC,MAAM,CAAC,CAQjB;AAwND;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CA8IpE"}
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/pull/command.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yCAAyC,CAAC;AAM3E,OAAO,EACL,eAAe,EAIhB,MAAM,wBAAwB,CAAC;AAiBhC;;GAEG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;GAa7B,CAAC;AAEF,eAAO,MAAM,cAAc;;;;;;;;;;;GAAgC,CAAC;AAE5D,MAAM,MAAM,QAAQ,GAAG,WAAW,CAAC,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC,CAAC;AAEzE;;GAEG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;;GAWxB,CAAC;AAEH;;GAEG;AACH,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzC;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,gCAAgC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,0CAA0C;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,oDAAoD;IACpD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,UAAU,CAKlE;AAED,UAAU,WAAW;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAkCD,sEAAsE;AACtE,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAmB/D;AA+CD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,MAAM,CAWjF;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,EAC1C,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,UAAU,GACjB,OAAO,CAAC,WAAW,EAAE,CAAC,CAoBxB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,MAAM,CAmBjG;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,EAC1C,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,UAAU,GACjB,OAAO,CAAC,MAAM,CAAC,CAKjB;AAkdD;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoKpE"}
@@ -7,11 +7,11 @@
7
7
  * @module cli/commands/pull
8
8
  */
9
9
  import { defineSchema, lazySchema } from "../../../src/schemas/index.js";
10
- import { dirname, join, normalize, resolve } from "../../../src/platform/compat/path/index.js";
10
+ import { dirname, isAbsolute, join, relative, resolve } from "../../../src/platform/compat/path/index.js";
11
+ import { isNotFoundError, lstat } from "../../../src/fs/index.js";
11
12
  import { cliLogger } from "../../utils/index.js";
12
13
  import { resolveCliApiUrl } from "../../shared/constants.js";
13
- import { cwd } from "../../../src/platform/index.js";
14
- import { createFileSystem } from "../../../src/platform/index.js";
14
+ import { createFileSystem, cwd, env, runCommand } from "../../../src/platform/index.js";
15
15
  import { createApiClient, readConfigFile, resolveConfigWithAuth, } from "../../shared/config.js";
16
16
  import { confirmPrompt, isTTY, logInfo, logSuccess, logWarning } from "../../utils/index.js";
17
17
  import { createNoopSpinner, createSpinner } from "../../ui/index.js";
@@ -20,6 +20,7 @@ import { getApiTokenEnv, getEnvironmentConfig } from "../../../src/config/index.
20
20
  import { ERROR_REGISTRY, getErrorBySlug, INVALID_ARGUMENT, RESOURCE_NOT_FOUND, VeryfrontError, } from "../../../src/errors/index.js";
21
21
  import { withSpan } from "../../../src/observability/tracing/otlp-setup.js";
22
22
  import { CommonArgs, createArgParser } from "../../shared/args.js";
23
+ import { createIgnoreChecker, loadIgnorePatterns } from "../../sync/ignore.js";
23
24
  /**
24
25
  * Schema factory for pull command arguments
25
26
  */
@@ -32,6 +33,7 @@ export const getPullArgsSchema = defineSchema((v) => v.object({
32
33
  release: v.string().optional(),
33
34
  force: v.boolean().default(false),
34
35
  dryRun: v.boolean().default(false),
36
+ prune: v.boolean().default(false),
35
37
  quiet: v.boolean().default(false),
36
38
  }));
37
39
  export const PullArgsSchema = lazySchema(getPullArgsSchema);
@@ -47,6 +49,7 @@ export const parsePullArgs = createArgParser(PullArgsSchema, {
47
49
  release: CommonArgs.release,
48
50
  force: CommonArgs.force,
49
51
  dryRun: CommonArgs.dryRun,
52
+ prune: { keys: ["prune"], type: "boolean" },
50
53
  quiet: CommonArgs.quiet,
51
54
  });
52
55
  /**
@@ -62,26 +65,59 @@ export function resolvePullSource(options) {
62
65
  return { type: "branch", name: options.branch };
63
66
  return { type: "main" };
64
67
  }
65
- /**
66
- * Validate and sanitize file path to prevent path traversal attacks.
67
- * Ensures the resolved path is within the project directory.
68
- *
69
- * @param filePath - The file path from API to validate
70
- * @param projectDir - The project directory base path
71
- * @returns Sanitized absolute path
72
- * @throws Error if path attempts to escape project directory
73
- */
74
- function validateFilePath(filePath, projectDir) {
75
- const normalizedPath = normalize(filePath);
76
- if (normalizedPath.startsWith("/") || normalizedPath.startsWith("..")) {
77
- throw new Error(`Invalid file path: "${filePath}" - paths must be relative and cannot escape project directory`);
68
+ /** Validate a remote file path as a canonical relative POSIX path. */
69
+ export function validateRemoteFilePath(filePath) {
70
+ if (filePath.length === 0 ||
71
+ filePath.includes("\\") ||
72
+ filePath.includes("\0") ||
73
+ isAbsolute(filePath)) {
74
+ throw new Error(`Invalid file path: "${filePath}" - expected a relative POSIX path`);
78
75
  }
79
- const fullPath = resolve(projectDir, normalizedPath);
76
+ const segments = filePath.split("/");
77
+ if (segments.some((segment) => segment === "" || segment === "." || segment === "..")) {
78
+ throw new Error(`Invalid file path: "${filePath}" - path must be canonical`);
79
+ }
80
+ if (segments.some((segment) => [".git", ".veryfront"].includes(segment.toLowerCase()))) {
81
+ throw new Error(`Invalid file path: "${filePath}" - reserved local metadata path`);
82
+ }
83
+ return filePath;
84
+ }
85
+ /** Resolve a validated remote path and reject local symlink traversal. */
86
+ async function validateFilePath(filePath, projectDir) {
87
+ const canonicalPath = validateRemoteFilePath(filePath);
80
88
  const resolvedProjectDir = resolve(projectDir);
81
- if (!fullPath.startsWith(resolvedProjectDir + "/") && fullPath !== resolvedProjectDir) {
89
+ const fullPath = resolve(resolvedProjectDir, canonicalPath);
90
+ const destinationRelativePath = relative(resolvedProjectDir, fullPath).replace(/\\/g, "/");
91
+ if (destinationRelativePath === ".." ||
92
+ destinationRelativePath.startsWith("../") ||
93
+ isAbsolute(destinationRelativePath)) {
82
94
  throw new Error(`Invalid file path: "${filePath}" - resolved path escapes project directory`);
83
95
  }
84
- return fullPath;
96
+ const segments = canonicalPath.split("/");
97
+ let currentPath = resolvedProjectDir;
98
+ for (const [index, segment] of segments.entries()) {
99
+ currentPath = join(currentPath, segment);
100
+ let info;
101
+ try {
102
+ info = await lstat(currentPath);
103
+ }
104
+ catch (error) {
105
+ if (isNotFoundError(error))
106
+ break;
107
+ throw error;
108
+ }
109
+ if (info.isSymlink) {
110
+ throw new Error(`Invalid file path: "${filePath}" - symbolic links are not allowed in pull destinations`);
111
+ }
112
+ const isFinal = index === segments.length - 1;
113
+ if (!isFinal && !info.isDirectory) {
114
+ throw new Error(`Invalid file path: "${filePath}" - a parent path is not a directory`);
115
+ }
116
+ if (isFinal && info.isDirectory) {
117
+ throw new Error(`Invalid file path: "${filePath}" - destination is a directory`);
118
+ }
119
+ }
120
+ return { path: fullPath, relativePath: canonicalPath };
85
121
  }
86
122
  /**
87
123
  * Build the files list URL based on pull source
@@ -143,19 +179,23 @@ export function buildFileContentUrl(projectSlug, path, source) {
143
179
  export async function getFileContent(client, projectSlug, path, source) {
144
180
  const url = buildFileContentUrl(projectSlug, path, source);
145
181
  const response = await client.get(url);
146
- // Ensure text files end with a trailing newline (POSIX standard)
147
- const content = response.content;
148
- if (content && !content.endsWith("\n"))
149
- return content + "\n";
150
- return content;
182
+ return response.content;
151
183
  }
152
184
  /** Concurrency limit for parallel file fetches */
153
185
  const CONCURRENCY = 20;
154
- async function processFile(op, client, projectSlug, source, fs) {
186
+ async function fetchFile(op, client, projectSlug, source) {
155
187
  try {
156
188
  const content = await getFileContent(client, projectSlug, op.relativePath, source);
189
+ return { success: true, op: { ...op, content } };
190
+ }
191
+ catch (error) {
192
+ return { success: false, path: op.relativePath, error: error };
193
+ }
194
+ }
195
+ async function writePreparedFile(op, fs) {
196
+ try {
157
197
  await fs.mkdir(dirname(op.path), { recursive: true });
158
- await fs.writeTextFile(op.path, content);
198
+ await fs.writeTextFile(op.path, op.content);
159
199
  return { success: true, path: op.relativePath };
160
200
  }
161
201
  catch (error) {
@@ -166,24 +206,176 @@ async function writeFiles(ops, client, projectSlug, source, dryRun) {
166
206
  if (dryRun) {
167
207
  for (const op of ops)
168
208
  cliLogger.info(` Would write: ${op.relativePath}`);
169
- return { written: ops.length, skipped: 0 };
209
+ return { written: ops.length, failed: 0 };
170
210
  }
171
211
  const fs = createFileSystem();
172
- let written = 0;
173
- let skipped = 0;
212
+ const prepared = [];
213
+ let failed = 0;
174
214
  for (let i = 0; i < ops.length; i += CONCURRENCY) {
175
215
  const batch = ops.slice(i, i + CONCURRENCY);
176
- const results = await Promise.all(batch.map((op) => processFile(op, client, projectSlug, source, fs)));
216
+ const results = await Promise.all(batch.map((op) => fetchFile(op, client, projectSlug, source)));
217
+ for (const result of results) {
218
+ if (result.success) {
219
+ prepared.push(result.op);
220
+ continue;
221
+ }
222
+ cliLogger.error(`Failed to fetch ${result.path}:`, result.error);
223
+ failed++;
224
+ }
225
+ }
226
+ if (failed > 0)
227
+ return { written: 0, failed };
228
+ let written = 0;
229
+ for (let i = 0; i < prepared.length; i += CONCURRENCY) {
230
+ const batch = prepared.slice(i, i + CONCURRENCY);
231
+ const results = await Promise.all(batch.map((op) => writePreparedFile(op, fs)));
177
232
  for (const result of results) {
178
233
  if (result.success) {
179
234
  written++;
180
235
  continue;
181
236
  }
182
237
  cliLogger.error(`Failed to write ${result.path}:`, result.error);
183
- skipped++;
238
+ failed++;
239
+ }
240
+ }
241
+ return { written, failed };
242
+ }
243
+ async function listManagedLocalFiles(projectDir, ignoreChecker) {
244
+ const fs = createFileSystem();
245
+ if (!(await fs.exists(projectDir)))
246
+ return [];
247
+ const files = [];
248
+ async function walk(currentDir) {
249
+ const entries = await fs.readDir(currentDir);
250
+ for await (const entry of entries) {
251
+ const path = join(currentDir, entry.name);
252
+ const relativePath = relative(projectDir, path).replace(/\\/g, "/");
253
+ if (ignoreChecker.isIgnored(relativePath))
254
+ continue;
255
+ if (entry.isSymlink) {
256
+ if (ignoreChecker.isSupportedExtension(entry.name)) {
257
+ throw new Error(`Veryfront pull with --prune does not support symbolic links: "${relativePath}". Remove the link and run veryfront pull again.`);
258
+ }
259
+ continue;
260
+ }
261
+ if (entry.isDirectory) {
262
+ await walk(path);
263
+ continue;
264
+ }
265
+ if (!ignoreChecker.isSupportedExtension(entry.name))
266
+ continue;
267
+ files.push({ path, relativePath });
268
+ }
269
+ }
270
+ await walk(projectDir);
271
+ return files;
272
+ }
273
+ async function deleteLocalFiles(ops, dryRun) {
274
+ if (dryRun) {
275
+ for (const op of ops)
276
+ cliLogger.info(` Would delete: ${op.relativePath}`);
277
+ return { deleted: ops.length, failed: 0 };
278
+ }
279
+ const fs = createFileSystem();
280
+ let deleted = 0;
281
+ let failed = 0;
282
+ for (const op of ops) {
283
+ try {
284
+ await fs.remove(op.path);
285
+ deleted++;
286
+ }
287
+ catch (error) {
288
+ cliLogger.error(`Failed to delete ${op.relativePath}:`, error);
289
+ failed++;
290
+ }
291
+ }
292
+ return { deleted, failed };
293
+ }
294
+ function cleanGitEnvironment() {
295
+ const gitEnv = env();
296
+ for (const key of Object.keys(gitEnv)) {
297
+ if (key.startsWith("GIT_"))
298
+ delete gitEnv[key];
299
+ }
300
+ return gitEnv;
301
+ }
302
+ async function nearestExistingDirectory(path) {
303
+ let current = resolve(path);
304
+ while (true) {
305
+ try {
306
+ const info = await lstat(current);
307
+ if (info.isDirectory)
308
+ return current;
309
+ }
310
+ catch (error) {
311
+ if (!isNotFoundError(error))
312
+ throw error;
313
+ }
314
+ const parent = dirname(current);
315
+ if (parent === current)
316
+ return null;
317
+ current = parent;
318
+ }
319
+ }
320
+ async function findGitRoot(projectDir) {
321
+ const cwd = await nearestExistingDirectory(projectDir);
322
+ if (!cwd)
323
+ return null;
324
+ try {
325
+ const repository = await runCommand("git", {
326
+ args: ["rev-parse", "--show-toplevel"],
327
+ cwd,
328
+ clearEnv: true,
329
+ env: cleanGitEnvironment(),
330
+ capture: true,
331
+ timeoutMs: 5_000,
332
+ });
333
+ const root = repository.stdout?.trim();
334
+ return repository.success && root ? resolve(root) : null;
335
+ }
336
+ catch {
337
+ return null;
338
+ }
339
+ }
340
+ function isUntrackedPushReceipt(statusLine) {
341
+ if (!statusLine.startsWith("?? "))
342
+ return false;
343
+ const path = statusLine.slice(3).replace(/\\/g, "/");
344
+ return path === ".veryfront/push-receipt.json" ||
345
+ path.endsWith("/.veryfront/push-receipt.json");
346
+ }
347
+ async function assertCleanGitWorktrees(projectDirs) {
348
+ const gitRoots = new Set();
349
+ for (const projectDir of projectDirs) {
350
+ const gitRoot = await findGitRoot(projectDir);
351
+ if (!gitRoot) {
352
+ throw INVALID_ARGUMENT.create({
353
+ detail: `Pull with --prune requires ${projectDir} to be inside a Git worktree. Clone or initialize the repository, or use --dry-run to preview without deleting files.`,
354
+ });
355
+ }
356
+ gitRoots.add(gitRoot);
357
+ }
358
+ for (const gitRoot of gitRoots) {
359
+ const status = await runCommand("git", {
360
+ args: ["status", "--porcelain=v1", "--untracked-files=all"],
361
+ cwd: gitRoot,
362
+ clearEnv: true,
363
+ env: cleanGitEnvironment(),
364
+ capture: true,
365
+ timeoutMs: 5_000,
366
+ });
367
+ if (!status.success) {
368
+ throw INVALID_ARGUMENT.create({
369
+ detail: "Veryfront could not verify that the Git worktree is clean before pruning files.",
370
+ });
371
+ }
372
+ const dirty = (status.stdout ?? "").split("\n").some((line) => line !== "" && !isUntrackedPushReceipt(line));
373
+ if (dirty) {
374
+ throw INVALID_ARGUMENT.create({
375
+ detail: `Pull with --prune requires a clean Git worktree at ${gitRoot}. Commit or stash local changes, then run the command again.`,
376
+ });
184
377
  }
185
378
  }
186
- return { written, skipped };
187
379
  }
188
380
  function formatPullSource(source) {
189
381
  switch (source.type) {
@@ -223,16 +415,22 @@ function pullProjectListError(projectSlug, sourceLabel, error) {
223
415
  }
224
416
  return wrapped;
225
417
  }
226
- async function confirmPullWrite(projectDir) {
418
+ async function confirmPullWrite(projectDir, writeCount, deleteCount) {
227
419
  if (isInteractive() && !isTTY()) {
228
420
  throw INVALID_ARGUMENT.create({
229
421
  detail: `Pull requires confirmation before writing files, but no interactive prompt is available. ` +
230
- `Re-run with --force to write into ${projectDir} without prompting.`,
422
+ `Re-run with --yes to write into ${projectDir} without prompting.`,
231
423
  });
232
424
  }
233
- return await confirmPrompt(`This will overwrite local files in ${projectDir}. Continue?`, false);
425
+ const actions = [];
426
+ if (writeCount > 0)
427
+ actions.push(`overwrite ${writeCount} local files`);
428
+ if (deleteCount > 0)
429
+ actions.push(`delete ${deleteCount} managed local files`);
430
+ const action = actions.join(" and ");
431
+ return await confirmPrompt(`This will ${action} in ${projectDir}. Continue?`, false);
234
432
  }
235
- async function pullSingleProject(projectSlug, projectDir, source, force, dryRun, config, quiet = false) {
433
+ async function pullSingleProject(projectSlug, projectDir, source, force, dryRun, prune, config, quiet = false) {
236
434
  const sourceLabel = formatPullSource(source);
237
435
  let spinner = quiet
238
436
  ? createNoopSpinner()
@@ -248,45 +446,75 @@ async function pullSingleProject(projectSlug, projectDir, source, force, dryRun,
248
446
  finally {
249
447
  spinner.stop();
250
448
  }
251
- if (files.length === 0) {
252
- if (!quiet)
253
- logInfo(`No files to pull from ${projectSlug}.`);
254
- return { written: 0, skipped: 0, cancelled: false };
255
- }
449
+ const pruneIgnoreChecker = prune
450
+ ? createIgnoreChecker(await loadIgnorePatterns(projectDir))
451
+ : null;
256
452
  const writeOps = [];
453
+ const remotePaths = new Set();
257
454
  for (const file of files) {
258
455
  try {
259
- writeOps.push({ path: validateFilePath(file.path, projectDir), relativePath: file.path });
456
+ const op = await validateFilePath(file.path, projectDir);
457
+ if (remotePaths.has(op.relativePath)) {
458
+ throw new Error(`Duplicate remote file path: "${op.relativePath}"`);
459
+ }
460
+ remotePaths.add(op.relativePath);
461
+ if (pruneIgnoreChecker &&
462
+ (pruneIgnoreChecker.isIgnored(op.relativePath) ||
463
+ !pruneIgnoreChecker.isSupportedExtension(op.relativePath))) {
464
+ continue;
465
+ }
466
+ writeOps.push(op);
260
467
  }
261
468
  catch (error) {
262
- if (!quiet)
263
- cliLogger.warn(`Skipping invalid file path: ${file.path}`, error);
469
+ throw INVALID_ARGUMENT.create({
470
+ detail: `Veryfront returned an invalid file path "${file.path}": ${describeError(error)}. No local files were changed.`,
471
+ cause: error,
472
+ });
264
473
  }
265
474
  }
475
+ let deleteOps = [];
476
+ if (pruneIgnoreChecker) {
477
+ const managedRemotePaths = new Set(writeOps.map((op) => op.relativePath));
478
+ const localFiles = await listManagedLocalFiles(projectDir, pruneIgnoreChecker);
479
+ deleteOps = localFiles.filter((file) => !managedRemotePaths.has(file.relativePath));
480
+ }
481
+ if (writeOps.length === 0 && deleteOps.length === 0) {
482
+ if (!quiet)
483
+ logInfo(`No files to pull from ${projectSlug}.`);
484
+ return { written: 0, deleted: 0, cancelled: false };
485
+ }
266
486
  if (!quiet) {
267
- cliLogger.info(`\nFound ${files.length} files to ${dryRun ? "pull" : "write"} from ${projectSlug}.`);
487
+ const deleteSummary = deleteOps.length > 0 ? ` and ${deleteOps.length} to delete` : "";
488
+ cliLogger.info(`\nFound ${writeOps.length} files to ${dryRun ? "pull" : "write"}${deleteSummary} from ${projectSlug}.`);
268
489
  }
269
490
  if (!force && !dryRun) {
270
- const confirmed = await confirmPullWrite(projectDir);
491
+ const confirmed = await confirmPullWrite(projectDir, writeOps.length, deleteOps.length);
271
492
  if (!confirmed) {
272
493
  cliLogger.info("Pull cancelled.");
273
- return { written: 0, skipped: 0, cancelled: true };
494
+ return { written: 0, deleted: 0, cancelled: true };
274
495
  }
275
496
  }
276
497
  spinner = quiet ? createNoopSpinner() : createSpinner(`Writing files to ${projectDir}...`);
277
- const result = await writeFiles(writeOps, client, projectSlug, source, dryRun);
498
+ const writeResult = await writeFiles(writeOps, client, projectSlug, source, dryRun);
499
+ if (writeResult.failed > 0) {
500
+ spinner.stop();
501
+ throw new Error(`Failed to pull ${writeResult.failed} file${writeResult.failed === 1 ? "" : "s"}. No local files were pruned. Review git status and restore a clean worktree before retrying if any files were written.`);
502
+ }
503
+ const deleteResult = await deleteLocalFiles(deleteOps, dryRun);
278
504
  spinner.stop();
505
+ if (deleteResult.failed > 0) {
506
+ throw new Error(`Failed to prune ${deleteResult.failed} local file${deleteResult.failed === 1 ? "" : "s"}. Some files may have changed. Review git status and restore a clean worktree before retrying.`);
507
+ }
279
508
  if (!quiet) {
280
509
  if (dryRun) {
281
- logInfo(`Dry run complete for ${projectSlug}. Would write ${result.written} files.`);
510
+ logInfo(`Dry run complete for ${projectSlug}. Would write ${writeResult.written} and delete ${deleteResult.deleted} files.`);
282
511
  }
283
512
  else {
284
- logSuccess(`Pulled ${result.written} files from ${projectSlug} (${sourceLabel}).`);
285
- if (result.skipped > 0)
286
- logWarning(`Skipped ${result.skipped} files due to errors.`);
513
+ const deletion = deleteResult.deleted > 0 ? ` and deleted ${deleteResult.deleted}` : "";
514
+ logSuccess(`Pulled ${writeResult.written} files${deletion} from ${projectSlug} (${sourceLabel}).`);
287
515
  }
288
516
  }
289
- return { ...result, cancelled: false };
517
+ return { written: writeResult.written, deleted: deleteResult.deleted, cancelled: false };
290
518
  }
291
519
  function formatProjectCount(count) {
292
520
  return `${count} project${count === 1 ? "" : "s"}`;
@@ -318,7 +546,7 @@ function structuredFailureAggregate(failedProjects, detail) {
318
546
  export function pullCommand(options = {}) {
319
547
  const source = resolvePullSource(options);
320
548
  return withSpan("cli.command.pull", async () => {
321
- const { projectSlug: slugOverride, projects: projectsOverride, projectDir = cwd(), force = false, dryRun = false, quiet = false, } = options;
549
+ const { projectSlug: slugOverride, projects: projectsOverride, projectDir = cwd(), force = false, dryRun = false, prune = false, quiet = false, } = options;
322
550
  const spinner = quiet ? createNoopSpinner() : createSpinner("Resolving configuration...");
323
551
  const configFile = await readConfigFile(projectDir);
324
552
  const projects = projectsOverride ?? configFile?.projects;
@@ -344,13 +572,26 @@ export function pullCommand(options = {}) {
344
572
  projectSlug: "",
345
573
  };
346
574
  }
575
+ if (prune && !dryRun) {
576
+ spinner.update("Checking Git worktree...");
577
+ const targetDirs = projects?.length
578
+ ? projects.map((project) => join(projectDir, project))
579
+ : [projectDir];
580
+ try {
581
+ await assertCleanGitWorktrees(targetDirs);
582
+ }
583
+ catch (error) {
584
+ spinner.stop();
585
+ throw error;
586
+ }
587
+ }
347
588
  spinner.stop();
348
589
  if (!projects?.length) {
349
- await pullSingleProject(config.projectSlug, projectDir, source, force, dryRun, config, quiet);
590
+ await pullSingleProject(config.projectSlug, projectDir, source, force, dryRun, prune, config, quiet);
350
591
  return;
351
592
  }
352
593
  let totalWritten = 0;
353
- let totalSkippedFiles = 0;
594
+ let totalDeleted = 0;
354
595
  const failedProjects = [];
355
596
  const cancelledProjects = [];
356
597
  for (const project of projects) {
@@ -358,9 +599,9 @@ export function pullCommand(options = {}) {
358
599
  if (!quiet)
359
600
  cliLogger.info(`\n--- Pulling ${project} into ${targetDir} ---`);
360
601
  try {
361
- const result = await pullSingleProject(project, targetDir, source, force, dryRun, config, quiet);
602
+ const result = await pullSingleProject(project, targetDir, source, force, dryRun, prune, config, quiet);
362
603
  totalWritten += result.written;
363
- totalSkippedFiles += result.skipped;
604
+ totalDeleted += result.deleted;
364
605
  if (result.cancelled)
365
606
  cancelledProjects.push(project);
366
607
  }
@@ -378,10 +619,14 @@ export function pullCommand(options = {}) {
378
619
  if (!quiet) {
379
620
  cliLogger.info("");
380
621
  if (dryRun) {
381
- logInfo(`Dry run complete. Would write ${totalWritten} files total across ${projects.length} projects.`);
622
+ logInfo(`Dry run complete. Would write ${totalWritten} and delete ${totalDeleted} files total across ${projects.length} projects.`);
382
623
  }
383
624
  else if (totalWritten > 0) {
384
- logSuccess(`Pulled ${totalWritten} files total across ${projects.length} projects.`);
625
+ const deletion = totalDeleted > 0 ? ` and deleted ${totalDeleted}` : "";
626
+ logSuccess(`Pulled ${totalWritten} files${deletion} total across ${projects.length} projects.`);
627
+ }
628
+ else if (totalDeleted > 0) {
629
+ logSuccess(`Deleted ${totalDeleted} files across ${projects.length} projects.`);
385
630
  }
386
631
  else {
387
632
  logInfo(`No files were pulled across ${projects.length} projects.`);
@@ -392,9 +637,6 @@ export function pullCommand(options = {}) {
392
637
  if (failedProjects.length > 0) {
393
638
  logWarning(`Failed to pull ${formatProjectCount(failedProjects.length)}: ${failedProjects.map(({ project }) => project).join(", ")}.`);
394
639
  }
395
- if (totalSkippedFiles > 0) {
396
- logWarning(`Skipped ${totalSkippedFiles} files due to write errors.`);
397
- }
398
640
  }
399
641
  if (failedProjects.length > 0) {
400
642
  const detail = failedProjectsDetail(failedProjects);
@@ -406,5 +648,9 @@ export function pullCommand(options = {}) {
406
648
  throw structuredError;
407
649
  throw new Error(detail);
408
650
  }
409
- }, { "cli.dryRun": options.dryRun ?? false, "cli.source_type": source.type });
651
+ }, {
652
+ "cli.dryRun": options.dryRun ?? false,
653
+ "cli.prune": options.prune ?? false,
654
+ "cli.source_type": source.type,
655
+ });
410
656
  }
@@ -1 +1 @@
1
- {"version":3,"file":"command-help.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/push/command-help.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,eAAO,MAAM,QAAQ,EAAE,WAyCtB,CAAC"}
1
+ {"version":3,"file":"command-help.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/push/command-help.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,eAAO,MAAM,QAAQ,EAAE,WA+CtB,CAAC"}
@@ -1,7 +1,7 @@
1
1
  export const pushHelp = {
2
2
  name: "push",
3
3
  category: "deploy",
4
- description: "Create a branch and upload local files to Veryfront",
4
+ description: "Upload local source files to a Veryfront branch",
5
5
  usage: "veryfront push [options]",
6
6
  options: [
7
7
  {
@@ -18,12 +18,16 @@ export const pushHelp = {
18
18
  },
19
19
  {
20
20
  flag: "-f, --force",
21
- description: "Push without confirmation",
21
+ description: "Skip confirmation for compatibility (prefer global --yes)",
22
22
  },
23
23
  {
24
24
  flag: "--dry-run",
25
25
  description: "Show what would be uploaded without uploading",
26
26
  },
27
+ {
28
+ flag: "-q, --quiet",
29
+ description: "Suppress progress and summary output",
30
+ },
27
31
  ],
28
32
  examples: [
29
33
  "veryfront push",
@@ -31,12 +35,14 @@ export const pushHelp = {
31
35
  "veryfront push --dir ./my-project",
32
36
  "veryfront push --branch feature-header",
33
37
  "veryfront push --branch main # Push directly to main",
38
+ "veryfront push --branch main --yes # Non-interactive CI push",
34
39
  "veryfront push --dry-run",
35
40
  ],
36
41
  notes: [
37
42
  "Requires VERYFRONT_API_TOKEN env var or veryfront.json config",
38
- "Creates a new branch for each push - merge in Studio",
39
- "Use --branch=main to push directly without creating a branch",
40
- "Uploads all files using their relative paths",
43
+ "Without --branch, creates a timestamped branch that you can review in Studio",
44
+ "Use --branch main to update the existing Veryfront main branch",
45
+ "Uploads supported text source files using their relative paths",
46
+ ".vfignore controls which supported files are excluded",
41
47
  ],
42
48
  };
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Push command - Upload local project files to a new Veryfront branch
2
+ * Push command - Upload local project files to a Veryfront branch
3
3
  *
4
4
  * Scans local files and uploads them to the API using relative paths.
5
- * Creates a new branch for the changes which can be merged in Studio.
5
+ * Creates a branch when the requested branch does not already exist.
6
6
  *
7
7
  * @module cli/commands/push
8
8
  */
@@ -52,7 +52,7 @@ export interface PushOptions {
52
52
  projectDir?: string;
53
53
  /** Branch name to create (auto-generated if not provided) */
54
54
  branch?: string;
55
- /** Force push without confirmation */
55
+ /** Skip confirmation without changing push semantics */
56
56
  force?: boolean;
57
57
  /** Dry run - show what would be uploaded without uploading */
58
58
  dryRun?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/push/command.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/push/command.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Push command - Upload local project files to a new Veryfront branch
2
+ * Push command - Upload local project files to a Veryfront branch
3
3
  *
4
4
  * Scans local files and uploads them to the API using relative paths.
5
- * Creates a new branch for the changes which can be merged in Studio.
5
+ * Creates a branch when the requested branch does not already exist.
6
6
  *
7
7
  * @module cli/commands/push
8
8
  */
@@ -19,6 +19,7 @@ import { withSpan } from "../../../src/observability/tracing/otlp-setup.js";
19
19
  import { createIgnoreChecker, loadIgnorePatterns } from "../../sync/ignore.js";
20
20
  import { listAllFiles } from "../pull/index.js";
21
21
  import { CommonArgs, createArgParser } from "../../shared/args.js";
22
+ import { isNotFoundError, lstat } from "../../../src/fs/index.js";
22
23
  import { areSourceFilesTracked, clearPushReceipt, computeSourceDigest, getProjectTarget, normalizeControlPlane, resolveGitSource, writePushReceipt, } from "../../shared/deployment-provenance.js";
23
24
  /**
24
25
  * Schema factory for push command arguments
@@ -107,12 +108,29 @@ function projectSlugConflictError(error, source) {
107
108
  }
108
109
  return new Error(`${error.message} ${action}, then run veryfront push again.`);
109
110
  }
111
+ async function sourceFilesForGitTracking(projectDir, files) {
112
+ const ignorePath = join(projectDir, ".vfignore");
113
+ let ignoreInfo;
114
+ try {
115
+ ignoreInfo = await lstat(ignorePath);
116
+ }
117
+ catch (error) {
118
+ if (isNotFoundError(error))
119
+ return files;
120
+ throw error;
121
+ }
122
+ if (ignoreInfo.isSymlink || !ignoreInfo.isFile) {
123
+ throw new Error(".vfignore must be a regular file inside the project and cannot be a symbolic link.");
124
+ }
125
+ return [...files, { path: ".vfignore", content: "" }];
126
+ }
110
127
  export async function capturePushSourceSnapshot(projectDir, ignoreChecker) {
111
128
  const gitSourceBefore = await resolveGitSource(projectDir);
112
129
  const files = await scanLocalFiles(projectDir, ignoreChecker);
130
+ const trackedSourceFiles = await sourceFilesForGitTracking(projectDir, files);
113
131
  const [sourceDigest, filesTracked] = await Promise.all([
114
132
  computeSourceDigest(files),
115
- areSourceFilesTracked(projectDir, files),
133
+ areSourceFilesTracked(projectDir, trackedSourceFiles),
116
134
  ]);
117
135
  const gitSource = await resolveGitSource(projectDir);
118
136
  if (!gitSourcesMatch(gitSourceBefore, gitSource))
@@ -409,14 +427,17 @@ export function pushCommand(options = {}) {
409
427
  spinner.update("Uploading files...");
410
428
  uploadResult = await uploadFiles(client, config.projectSlug, branchId, ops, false);
411
429
  }
430
+ if (uploadResult.failed > 0) {
431
+ spinner.stop();
432
+ throw new Error(`Push failed for ${uploadResult.failed} file${uploadResult.failed === 1 ? "" : "s"}. Remote files were not deleted.`);
433
+ }
412
434
  if (toDelete.length > 0) {
413
435
  spinner.update("Deleting removed files...");
414
436
  deleteResult = await deleteFiles(client, config.projectSlug, branchId, toDelete, false);
415
437
  }
416
- const failedTotal = uploadResult.failed + deleteResult.failed;
417
- if (failedTotal > 0) {
438
+ if (deleteResult.failed > 0) {
418
439
  spinner.stop();
419
- throw new Error(`Push failed for ${failedTotal} file${failedTotal === 1 ? "" : "s"}`);
440
+ throw new Error(`Push failed for ${deleteResult.failed} file${deleteResult.failed === 1 ? "" : "s"} during deletion`);
420
441
  }
421
442
  spinner.update("Verifying push target...");
422
443
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"tips.d.ts","sourceRoot":"","sources":["../../../src/cli/help/tips.ts"],"names":[],"mappings":"AAGA,wBAAgB,UAAU,IAAI,MAAM,CAKnC;AAED,wBAAgB,YAAY,IAAI,MAAM,CAKrC;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CASzC;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAIzC;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAG1C;AAED,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAI3D;AAQD,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAElE"}
1
+ {"version":3,"file":"tips.d.ts","sourceRoot":"","sources":["../../../src/cli/help/tips.ts"],"names":[],"mappings":"AAGA,wBAAgB,UAAU,IAAI,MAAM,CAKnC;AAED,wBAAgB,YAAY,IAAI,MAAM,CAKrC;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CASzC;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CASzC;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAG1C;AAED,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAI3D;AAQD,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAElE"}
@@ -24,8 +24,9 @@ export function getInitTemplates() {
24
24
  }
25
25
  export function getPostBuildTips() {
26
26
  return `\n ${dim("Next steps:")}\n` +
27
- ` ${dim("•")} ${cyan("veryfront serve")} Preview locally\n` +
28
- ` ${dim("•")} ${cyan("veryfront deploy")} Deploy to production\n`;
27
+ ` ${dim("•")} ${cyan("veryfront serve")} Preview locally\n` +
28
+ ` ${dim("•")} ${cyan("veryfront push --branch main")} Upload source\n` +
29
+ ` ${dim("•")} ${cyan("veryfront deploy --branch main --env production")} Create a release and deploy\n`;
29
30
  }
30
31
  export function getPostDeployTips() {
31
32
  return `\n ${dim("Next steps:")}\n` +
@@ -73,30 +73,30 @@ Create a Veryfront app with AI capabilities.
73
73
  {
74
74
  metadata: {
75
75
  name: "deploy-safely",
76
- description: "Build, test, deploy, and verify with rollback on failure",
76
+ description: "Build, test, push, deploy, and verify with rollback through Git on failure",
77
77
  metadata: { version: "1.0.0" },
78
78
  },
79
79
  skillMd: `# Deploy Safely
80
80
 
81
- Build, test, deploy, and verify with rollback on failure.
81
+ Build and test the reviewed Git source, then push it to Veryfront before creating a release and deployment. If verification fails, revert the Git commit and run the normal delivery sequence again.
82
82
 
83
83
  ## Steps
84
84
 
85
- 1. \`veryfront build --json\`, abort if success: false
85
+ 1. \`veryfront build --json\`, abort if success is false
86
86
  2. \`veryfront test --json\`, abort if any test fails
87
- 3. \`veryfront push --branch <branch> --force\`, abort if any upload fails
87
+ 3. \`veryfront push --branch <branch> --yes\`, abort if any upload fails
88
88
  4. \`veryfront deploy --env <environment> --branch <branch> --yes --json\`
89
89
  5. Record the project, environment, release, deployment, and commit IDs
90
90
  6. Use vf_get_errors to verify no runtime errors after deploy
91
- 7. If errors: redeploy previous version
91
+ 7. If errors: run \`git revert <bad-commit>\` and \`git push origin <branch>\`, then let CI run steps 3 and 4 or repeat them manually
92
92
 
93
93
  ## Error Recovery
94
94
 
95
95
  - **Build fails**: Check vf_get_errors, fix and retry
96
- - **Tests fail**: Read JSON output, fix failing tests
97
- - **Push fails**: Fix failed uploads before retrying deploy
96
+ - **Tests fail**: Read the JSON output, fix failing tests
97
+ - **Push fails**: Fix the upload failure and rerun Push before Deploy
98
98
  - **Deploy fails**: Check environment, auth, branch
99
- - **Post-deploy errors**: Redeploy previous release`,
99
+ - **Post-deploy errors**: Revert the failing Git commit, push the revert, then run the normal Push and Deploy sequence`,
100
100
  directory: "core:deploy-safely",
101
101
  },
102
102
  {
@@ -1 +1 @@
1
- {"version":3,"file":"ignore.d.ts","sourceRoot":"","sources":["../../../src/cli/sync/ignore.ts"],"names":[],"mappings":"AAAA;;GAEG;AAyDH,MAAM,WAAW,aAAa;IAC5B,wCAAwC;IACxC,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;IAEzC,6CAA6C;IAC7C,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;CACjD;AAOD;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAoB/E;AAmED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,aAAa,CAyB9E;AAED;;GAEG;AACH,wBAAgB,0BAA0B,IAAI,aAAa,CAE1D"}
1
+ {"version":3,"file":"ignore.d.ts","sourceRoot":"","sources":["../../../src/cli/sync/ignore.ts"],"names":[],"mappings":"AAAA;;GAEG;AA0DH,MAAM,WAAW,aAAa;IAC5B,wCAAwC;IACxC,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;IAEzC,6CAA6C;IAC7C,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;CACjD;AAOD;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAsC/E;AAmED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,aAAa,CAyB9E;AAED;;GAEG;AACH,wBAAgB,0BAA0B,IAAI,aAAa,CAE1D"}
@@ -4,6 +4,7 @@
4
4
  import { join } from "../../src/platform/compat/path/index.js";
5
5
  import { createFileSystem } from "../../src/platform/index.js";
6
6
  import { cliLogger } from "../utils/index.js";
7
+ import { isNotFoundError, lstat } from "../../src/fs/index.js";
7
8
  /** Default patterns always ignored */
8
9
  const DEFAULT_IGNORE_PATTERNS = [
9
10
  // Directories
@@ -59,9 +60,22 @@ export async function loadIgnorePatterns(projectPath) {
59
60
  const fs = createFileSystem();
60
61
  const ignorePath = join(projectPath, ".vfignore");
61
62
  const patterns = [...DEFAULT_IGNORE_PATTERNS];
63
+ let ignoreInfo;
62
64
  try {
63
- if (!(await fs.exists(ignorePath)))
65
+ ignoreInfo = await lstat(ignorePath);
66
+ }
67
+ catch (error) {
68
+ if (isNotFoundError(error))
64
69
  return patterns;
70
+ cliLogger.debug("Failed to read .vfignore:", error);
71
+ throw new Error("Failed to read .vfignore. Fix the file permissions or path and try again.", {
72
+ cause: error,
73
+ });
74
+ }
75
+ if (ignoreInfo.isSymlink || !ignoreInfo.isFile) {
76
+ throw new Error(".vfignore must be a regular file inside the project and cannot be a symbolic link.");
77
+ }
78
+ try {
65
79
  const content = await fs.readTextFile(ignorePath);
66
80
  const customPatterns = content
67
81
  .split("\n")
@@ -71,6 +85,9 @@ export async function loadIgnorePatterns(projectPath) {
71
85
  }
72
86
  catch (error) {
73
87
  cliLogger.debug("Failed to read .vfignore:", error);
88
+ throw new Error("Failed to read .vfignore. Fix the file permissions or path and try again.", {
89
+ cause: error,
90
+ });
74
91
  }
75
92
  return patterns;
76
93
  }
@@ -1 +1 @@
1
- {"version":3,"file":"env-prompt.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/env-prompt.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAG1D,MAAM,WAAW,gBAAgB;IAC/B,6DAA6D;IAC7D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,yCAAyC;IACzC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,gEAAgE;IAChE,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,eAAe;IAC9B,4BAA4B;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,oCAAoC;IACpC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAOD;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,YAAY,EAAE,EACvB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,eAAe,CAAC,CAiF1B;AAkCD;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,CA0BzE"}
1
+ {"version":3,"file":"env-prompt.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/env-prompt.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAG1D,MAAM,WAAW,gBAAgB;IAC/B,6DAA6D;IAC7D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,yCAAyC;IACzC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,gEAAgE;IAChE,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,eAAe;IAC9B,4BAA4B;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,oCAAoC;IACpC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAOD;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,YAAY,EAAE,EACvB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,eAAe,CAAC,CAiF1B;AAkCD;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,CA0CzE"}
@@ -115,13 +115,15 @@ async function promptForSingleEnvVar(envVar) {
115
115
  * Generates content for .gitignore to ensure .env is not committed
116
116
  */
117
117
  export function generateGitignoreContent(existingContent) {
118
- const requiredEntries = ["# Environment files", ".env", ".env.local", ".env.*.local", ""];
118
+ const environmentEntries = [".env", ".env.local", ".env.*.local"];
119
119
  if (!existingContent) {
120
120
  return [
121
121
  "# Dependencies",
122
122
  "node_modules/",
123
123
  "",
124
- ...requiredEntries,
124
+ "# Environment files",
125
+ ...environmentEntries,
126
+ "",
125
127
  "# Build output",
126
128
  "dist/",
127
129
  ".veryfront/",
@@ -135,7 +137,18 @@ export function generateGitignoreContent(existingContent) {
135
137
  "",
136
138
  ].join("\n");
137
139
  }
138
- if (existingContent.includes(".env"))
140
+ const existingEntries = new Set(existingContent.split(/\r?\n/).map((line) => line.trim()).filter(Boolean));
141
+ const additions = [];
142
+ const missingEnvironmentEntries = environmentEntries.filter((entry) => !existingEntries.has(entry));
143
+ if (missingEnvironmentEntries.length > 0) {
144
+ additions.push("# Environment files", ...missingEnvironmentEntries);
145
+ }
146
+ if (!existingEntries.has(".veryfront/") && !existingEntries.has(".veryfront")) {
147
+ if (additions.length > 0)
148
+ additions.push("");
149
+ additions.push("# Veryfront local state", ".veryfront/");
150
+ }
151
+ if (additions.length === 0)
139
152
  return existingContent;
140
- return `${existingContent.trimEnd()}\n\n${requiredEntries.join("\n")}`;
153
+ return `${existingContent.trimEnd()}\n\n${additions.join("\n")}\n`;
141
154
  }
package/esm/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.1062",
3
+ "version": "0.1.1063",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "minimumDependencyAge": {
@@ -1,3 +1,3 @@
1
1
  /** Shared version value. */
2
- export declare const VERSION = "0.1.1062";
2
+ export declare const VERSION = "0.1.1063";
3
3
  //# sourceMappingURL=version-constant.d.ts.map
@@ -1,4 +1,4 @@
1
1
  // Keep in sync with deno.json version.
2
2
  // scripts/release.ts updates this constant during releases.
3
3
  /** Shared version value. */
4
- export const VERSION = "0.1.1062";
4
+ export const VERSION = "0.1.1063";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.1062",
3
+ "version": "0.1.1063",
4
4
  "description": "The simplest way to build AI-powered apps",
5
5
  "keywords": [
6
6
  "react",
@@ -328,10 +328,10 @@
328
328
  "@types/react": "19.2.14",
329
329
  "@types/react-dom": "19.2.3",
330
330
  "ws": "8.21.0",
331
- "@veryfront/ext-bundler-esbuild": "0.1.1062",
332
- "@veryfront/ext-content-mdx": "0.1.1062",
333
- "@veryfront/ext-css-tailwind": "0.1.1062",
334
- "@veryfront/ext-parser-babel": "0.1.1062"
331
+ "@veryfront/ext-bundler-esbuild": "0.1.1063",
332
+ "@veryfront/ext-content-mdx": "0.1.1063",
333
+ "@veryfront/ext-css-tailwind": "0.1.1063",
334
+ "@veryfront/ext-parser-babel": "0.1.1063"
335
335
  },
336
336
  "devDependencies": {
337
337
  "@types/node": "20.9.0"