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.
- package/esm/cli/commands/demo/demo.d.ts.map +1 -1
- package/esm/cli/commands/demo/demo.js +2 -1
- package/esm/cli/commands/demo/steps.js +4 -4
- package/esm/cli/commands/deploy/command-help.d.ts.map +1 -1
- package/esm/cli/commands/deploy/command-help.js +6 -1
- package/esm/cli/commands/init/init-command.d.ts.map +1 -1
- package/esm/cli/commands/init/init-command.js +5 -4
- package/esm/cli/commands/pull/command-help.d.ts.map +1 -1
- package/esm/cli/commands/pull/command-help.js +15 -3
- package/esm/cli/commands/pull/command.d.ts +8 -1
- package/esm/cli/commands/pull/command.d.ts.map +1 -1
- package/esm/cli/commands/pull/command.js +310 -64
- package/esm/cli/commands/push/command-help.d.ts.map +1 -1
- package/esm/cli/commands/push/command-help.js +11 -5
- package/esm/cli/commands/push/command.d.ts +3 -3
- package/esm/cli/commands/push/command.d.ts.map +1 -1
- package/esm/cli/commands/push/command.js +27 -6
- package/esm/cli/help/tips.d.ts.map +1 -1
- package/esm/cli/help/tips.js +3 -2
- package/esm/cli/skills/core-skills.js +8 -8
- package/esm/cli/sync/ignore.d.ts.map +1 -1
- package/esm/cli/sync/ignore.js +18 -1
- package/esm/cli/utils/env-prompt.d.ts.map +1 -1
- package/esm/cli/utils/env-prompt.js +17 -4
- package/esm/deno.js +1 -1
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/package.json +5 -5
|
@@ -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,
|
|
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
|
|
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: "
|
|
43
|
+
title: "Create a Release and Deploy",
|
|
44
44
|
description: [
|
|
45
|
-
"
|
|
46
|
-
"
|
|
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,
|
|
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: "
|
|
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,
|
|
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.
|
|
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.
|
|
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.
|
|
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")}
|
|
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,
|
|
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: "
|
|
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
|
|
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 --
|
|
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
|
-
/**
|
|
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;
|
|
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,
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
209
|
+
return { written: ops.length, failed: 0 };
|
|
170
210
|
}
|
|
171
211
|
const fs = createFileSystem();
|
|
172
|
-
|
|
173
|
-
let
|
|
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) =>
|
|
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
|
-
|
|
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 --
|
|
422
|
+
`Re-run with --yes to write into ${projectDir} without prompting.`,
|
|
231
423
|
});
|
|
232
424
|
}
|
|
233
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
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
|
-
|
|
263
|
-
|
|
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
|
-
|
|
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,
|
|
494
|
+
return { written: 0, deleted: 0, cancelled: true };
|
|
274
495
|
}
|
|
275
496
|
}
|
|
276
497
|
spinner = quiet ? createNoopSpinner() : createSpinner(`Writing files to ${projectDir}...`);
|
|
277
|
-
const
|
|
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 ${
|
|
510
|
+
logInfo(`Dry run complete for ${projectSlug}. Would write ${writeResult.written} and delete ${deleteResult.deleted} files.`);
|
|
282
511
|
}
|
|
283
512
|
else {
|
|
284
|
-
|
|
285
|
-
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}, {
|
|
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,
|
|
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: "
|
|
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: "
|
|
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
|
-
"
|
|
39
|
-
"Use --branch
|
|
40
|
-
"Uploads
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yCAAyC,CAAC;AAK3E,OAAO,EACL,KAAK,SAAS,EAId,KAAK,cAAc,EAEpB,MAAM,wBAAwB,CAAC;AAKhC,OAAO,EAAuB,KAAK,aAAa,EAAsB,MAAM,sBAAsB,CAAC;AACnG,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
417
|
-
if (failedTotal > 0) {
|
|
438
|
+
if (deleteResult.failed > 0) {
|
|
418
439
|
spinner.stop();
|
|
419
|
-
throw new Error(`Push failed for ${
|
|
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,
|
|
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"}
|
package/esm/cli/help/tips.js
CHANGED
|
@@ -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")}
|
|
28
|
-
` ${dim("•")} ${cyan("veryfront
|
|
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
|
|
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
|
|
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> --
|
|
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:
|
|
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
|
|
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**:
|
|
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;
|
|
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"}
|
package/esm/cli/sync/ignore.js
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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${
|
|
153
|
+
return `${existingContent.trimEnd()}\n\n${additions.join("\n")}\n`;
|
|
141
154
|
}
|
package/esm/deno.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "veryfront",
|
|
3
|
-
"version": "0.1.
|
|
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.
|
|
332
|
-
"@veryfront/ext-content-mdx": "0.1.
|
|
333
|
-
"@veryfront/ext-css-tailwind": "0.1.
|
|
334
|
-
"@veryfront/ext-parser-babel": "0.1.
|
|
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"
|