@butterbase/cli 0.3.5 → 0.5.0

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.
Files changed (82) hide show
  1. package/README.md +407 -407
  2. package/dist/bin/butterbase.js +144 -2
  3. package/dist/bin/butterbase.js.map +1 -1
  4. package/dist/src/commands/agents.d.ts +12 -0
  5. package/dist/src/commands/agents.d.ts.map +1 -0
  6. package/dist/src/commands/agents.js +192 -0
  7. package/dist/src/commands/agents.js.map +1 -0
  8. package/dist/src/commands/apps.d.ts +2 -0
  9. package/dist/src/commands/apps.d.ts.map +1 -1
  10. package/dist/src/commands/apps.js +41 -1
  11. package/dist/src/commands/apps.js.map +1 -1
  12. package/dist/src/commands/clone.d.ts +9 -0
  13. package/dist/src/commands/clone.d.ts.map +1 -0
  14. package/dist/src/commands/clone.js +131 -0
  15. package/dist/src/commands/clone.js.map +1 -0
  16. package/dist/src/commands/config.d.ts.map +1 -1
  17. package/dist/src/commands/config.js +23 -2
  18. package/dist/src/commands/config.js.map +1 -1
  19. package/dist/src/commands/functions.d.ts +4 -0
  20. package/dist/src/commands/functions.d.ts.map +1 -1
  21. package/dist/src/commands/functions.js +23 -1
  22. package/dist/src/commands/functions.js.map +1 -1
  23. package/dist/src/commands/init.js +1 -1
  24. package/dist/src/commands/plugin.js +2 -2
  25. package/dist/src/commands/plugin.js.map +1 -1
  26. package/dist/src/commands/repo.d.ts +28 -0
  27. package/dist/src/commands/repo.d.ts.map +1 -0
  28. package/dist/src/commands/repo.js +440 -0
  29. package/dist/src/commands/repo.js.map +1 -0
  30. package/dist/src/commands/status.d.ts.map +1 -1
  31. package/dist/src/commands/status.js +8 -1
  32. package/dist/src/commands/status.js.map +1 -1
  33. package/dist/src/commands/templates.d.ts +9 -0
  34. package/dist/src/commands/templates.d.ts.map +1 -0
  35. package/dist/src/commands/templates.js +74 -0
  36. package/dist/src/commands/templates.js.map +1 -0
  37. package/dist/src/commands/visibility.d.ts +7 -0
  38. package/dist/src/commands/visibility.d.ts.map +1 -0
  39. package/dist/src/commands/visibility.js +32 -0
  40. package/dist/src/commands/visibility.js.map +1 -0
  41. package/dist/src/lib/api-client.d.ts +21 -1
  42. package/dist/src/lib/api-client.d.ts.map +1 -1
  43. package/dist/src/lib/api-client.js +91 -30
  44. package/dist/src/lib/api-client.js.map +1 -1
  45. package/dist/src/lib/config.d.ts +9 -0
  46. package/dist/src/lib/config.d.ts.map +1 -1
  47. package/dist/src/lib/config.js +41 -6
  48. package/dist/src/lib/config.js.map +1 -1
  49. package/dist/src/lib/repo-api.d.ts +123 -0
  50. package/dist/src/lib/repo-api.d.ts.map +1 -0
  51. package/dist/src/lib/repo-api.js +75 -0
  52. package/dist/src/lib/repo-api.js.map +1 -0
  53. package/dist/src/lib/repo-ignore.d.ts +17 -0
  54. package/dist/src/lib/repo-ignore.d.ts.map +1 -0
  55. package/dist/src/lib/repo-ignore.js +44 -0
  56. package/dist/src/lib/repo-ignore.js.map +1 -0
  57. package/dist/src/lib/repo-manifest.d.ts +12 -0
  58. package/dist/src/lib/repo-manifest.d.ts.map +1 -0
  59. package/dist/src/lib/repo-manifest.js +25 -0
  60. package/dist/src/lib/repo-manifest.js.map +1 -0
  61. package/dist/src/lib/repo-paths.d.ts +14 -0
  62. package/dist/src/lib/repo-paths.d.ts.map +1 -0
  63. package/dist/src/lib/repo-paths.js +41 -0
  64. package/dist/src/lib/repo-paths.js.map +1 -0
  65. package/dist/src/lib/repo-walk.d.ts +14 -0
  66. package/dist/src/lib/repo-walk.d.ts.map +1 -0
  67. package/dist/src/lib/repo-walk.js +36 -0
  68. package/dist/src/lib/repo-walk.js.map +1 -0
  69. package/dist/tsconfig.tsbuildinfo +1 -1
  70. package/package.json +55 -54
  71. package/templates/react-vite/.env.example +2 -2
  72. package/templates/react-vite/.mcp.json +10 -10
  73. package/templates/react-vite/README.md +38 -38
  74. package/templates/react-vite/index.html +13 -13
  75. package/templates/react-vite/package.json +25 -25
  76. package/templates/react-vite/src/App.css +33 -33
  77. package/templates/react-vite/src/App.tsx +77 -77
  78. package/templates/react-vite/src/index.css +8 -8
  79. package/templates/react-vite/src/lib.ts +6 -6
  80. package/templates/react-vite/src/main.tsx +10 -10
  81. package/templates/react-vite/tsconfig.json +21 -21
  82. package/templates/react-vite/vite.config.ts +6 -6
@@ -0,0 +1,123 @@
1
+ export interface FileEntry {
2
+ path: string;
3
+ sha256: string;
4
+ size: number;
5
+ mode?: number;
6
+ }
7
+ export interface PrepareResponse {
8
+ snapshot_id: string;
9
+ total_bytes: number;
10
+ file_count: number;
11
+ missing_blobs: {
12
+ sha256: string;
13
+ uploadUrl: string;
14
+ }[];
15
+ }
16
+ export interface CommitResponse {
17
+ snapshot_id: string;
18
+ total_bytes: number;
19
+ file_count: number;
20
+ }
21
+ export interface Manifest {
22
+ files: FileEntry[];
23
+ message?: string;
24
+ }
25
+ export interface SnapshotResponse {
26
+ snapshot_id: string;
27
+ manifest: Manifest;
28
+ }
29
+ export interface SnapshotListItem {
30
+ snapshot_id: string;
31
+ created_at: string;
32
+ }
33
+ export interface BlobUrlResponse {
34
+ sha256: string;
35
+ size: number;
36
+ downloadUrl: string;
37
+ expiresIn: number;
38
+ }
39
+ export interface CloneJob {
40
+ job_id: string;
41
+ status: 'pending' | 'processing' | 'completed' | 'failed';
42
+ source_app_id: string;
43
+ dest_app_id: string | null;
44
+ retry_count: number;
45
+ error_message: string | null;
46
+ created_at: string;
47
+ completed_at: string | null;
48
+ warnings: string[];
49
+ }
50
+ export interface TemplateRow {
51
+ app_id: string;
52
+ name: string;
53
+ region: string;
54
+ owner_id: string;
55
+ owner_display_name: string | null;
56
+ created_at: string;
57
+ fork_count: number;
58
+ has_repo: boolean;
59
+ schema_summary: {
60
+ table_count: number;
61
+ function_count: number;
62
+ };
63
+ }
64
+ export interface ListTemplatesResponse {
65
+ items: TemplateRow[];
66
+ total: number;
67
+ limit: number;
68
+ offset: number;
69
+ }
70
+ export declare const repoApi: {
71
+ prepare(appId: string, body: {
72
+ files: FileEntry[];
73
+ message?: string;
74
+ }): Promise<PrepareResponse>;
75
+ commit(appId: string, manifest: {
76
+ files: FileEntry[];
77
+ message?: string;
78
+ }): Promise<CommitResponse>;
79
+ getLatest(appId: string): Promise<SnapshotResponse>;
80
+ getSnapshot(appId: string, snapshotId: string): Promise<SnapshotResponse>;
81
+ listSnapshots(appId: string): Promise<{
82
+ snapshots: SnapshotListItem[];
83
+ }>;
84
+ getBlobUrl(appId: string, sha256: string): Promise<BlobUrlResponse>;
85
+ batchBlobUrls(appId: string, shas: string[]): Promise<{
86
+ blobs: {
87
+ sha256: string;
88
+ size: number;
89
+ downloadUrl: string;
90
+ expiresIn: number;
91
+ }[];
92
+ }>;
93
+ wipe(appId: string): Promise<{
94
+ message: string;
95
+ app_id: string;
96
+ }>;
97
+ };
98
+ export declare const cloneApi: {
99
+ create(sourceAppId: string, body: {
100
+ name?: string;
101
+ region?: string;
102
+ }): Promise<{
103
+ job_id: string;
104
+ status: string;
105
+ }>;
106
+ get(jobId: string): Promise<CloneJob>;
107
+ retry(jobId: string): Promise<{
108
+ job_id: string;
109
+ status: string;
110
+ }>;
111
+ listTemplates(opts?: {
112
+ q?: string;
113
+ sort?: "recent" | "popular";
114
+ region?: string;
115
+ limit?: number;
116
+ offset?: number;
117
+ }): Promise<ListTemplatesResponse>;
118
+ };
119
+ /** Raw PUT to a presigned S3 URL. No auth header — URL is signed. */
120
+ export declare function uploadBlob(presignedUrl: string, body: Buffer): Promise<void>;
121
+ /** Fetch a blob via the presigned GET URL returned by getBlobUrl. */
122
+ export declare function downloadBlob(presignedUrl: string): Promise<Buffer>;
123
+ //# sourceMappingURL=repo-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo-api.d.ts","sourceRoot":"","sources":["../../../src/lib/repo-api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ // submodules/butterbase-oss/packages/cli/src/lib/repo-api.ts
2
+ import { apiGet, apiPost, apiDelete } from './api-client.js';
3
+ export const repoApi = {
4
+ prepare(appId, body) {
5
+ return apiPost(`/v1/${appId}/repo/snapshots/prepare`, body);
6
+ },
7
+ commit(appId, manifest) {
8
+ return apiPost(`/v1/${appId}/repo/snapshots/commit`, { manifest });
9
+ },
10
+ getLatest(appId) {
11
+ return apiGet(`/v1/${appId}/repo/snapshots/latest`);
12
+ },
13
+ getSnapshot(appId, snapshotId) {
14
+ return apiGet(`/v1/${appId}/repo/snapshots/${snapshotId}`);
15
+ },
16
+ listSnapshots(appId) {
17
+ return apiGet(`/v1/${appId}/repo/snapshots`);
18
+ },
19
+ getBlobUrl(appId, sha256) {
20
+ return apiGet(`/v1/${appId}/repo/blobs/${sha256}`);
21
+ },
22
+ batchBlobUrls(appId, shas) {
23
+ return apiPost(`/v1/${appId}/repo/blobs/batch`, { shas });
24
+ },
25
+ wipe(appId) {
26
+ return apiDelete(`/v1/${appId}/repo`);
27
+ },
28
+ };
29
+ export const cloneApi = {
30
+ create(sourceAppId, body) {
31
+ return apiPost(`/v1/templates/${sourceAppId}/clone`, body);
32
+ },
33
+ get(jobId) {
34
+ return apiGet(`/v1/clone-jobs/${jobId}`);
35
+ },
36
+ retry(jobId) {
37
+ return apiPost(`/v1/clone-jobs/${jobId}/retry`, {});
38
+ },
39
+ listTemplates(opts = {}) {
40
+ const params = new URLSearchParams();
41
+ if (opts.q)
42
+ params.set('q', opts.q);
43
+ if (opts.sort)
44
+ params.set('sort', opts.sort);
45
+ if (opts.region)
46
+ params.set('region', opts.region);
47
+ if (opts.limit !== undefined)
48
+ params.set('limit', String(opts.limit));
49
+ if (opts.offset !== undefined)
50
+ params.set('offset', String(opts.offset));
51
+ const qs = params.toString();
52
+ return apiGet(`/v1/templates${qs ? `?${qs}` : ''}`);
53
+ },
54
+ };
55
+ /** Raw PUT to a presigned S3 URL. No auth header — URL is signed. */
56
+ export async function uploadBlob(presignedUrl, body) {
57
+ const res = await fetch(presignedUrl, {
58
+ method: 'PUT',
59
+ body: new Uint8Array(body),
60
+ headers: { 'content-type': 'application/octet-stream' },
61
+ });
62
+ if (!res.ok) {
63
+ const txt = await res.text().catch(() => '');
64
+ throw new Error(`Presigned PUT failed (${res.status}): ${txt.slice(0, 300)}`);
65
+ }
66
+ }
67
+ /** Fetch a blob via the presigned GET URL returned by getBlobUrl. */
68
+ export async function downloadBlob(presignedUrl) {
69
+ const res = await fetch(presignedUrl);
70
+ if (!res.ok)
71
+ throw new Error(`Presigned GET failed (${res.status})`);
72
+ const ab = await res.arrayBuffer();
73
+ return Buffer.from(ab);
74
+ }
75
+ //# sourceMappingURL=repo-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo-api.js","sourceRoot":"","sources":["../../../src/lib/repo-api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { type Ignore } from 'ignore';
2
+ /** Hardcoded defaults that always apply unless explicitly un-ignored in .butterbaseignore. */
3
+ export declare const HARDCODED_IGNORES: string[];
4
+ /**
5
+ * Load ignore rules from `root`.
6
+ *
7
+ * Precedence (highest wins): .butterbaseignore > .gitignore > HARDCODED_IGNORES.
8
+ * The `ignore` package treats later add()s as overriding earlier ones, and supports `!path`
9
+ * negations to un-ignore. So we add in order defaults → .gitignore → .butterbaseignore.
10
+ */
11
+ export declare function loadIgnoreRules(root: string): Promise<Ignore>;
12
+ /**
13
+ * Test whether a posix-style relative path is ignored.
14
+ * Pass directory paths with a trailing slash, e.g. `foo/`.
15
+ */
16
+ export declare function isIgnored(ig: Ignore, posixRelPath: string): boolean;
17
+ //# sourceMappingURL=repo-ignore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo-ignore.d.ts","sourceRoot":"","sources":["../../../src/lib/repo-ignore.ts"],"names":[],"mappings":"AAGA,OAAe,EAAE,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE7C,8FAA8F;AAC9F,eAAO,MAAM,iBAAiB,UAS7B,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAenE;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAGnE"}
@@ -0,0 +1,44 @@
1
+ // submodules/butterbase-oss/packages/cli/src/lib/repo-ignore.ts
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ import ignore from 'ignore';
5
+ /** Hardcoded defaults that always apply unless explicitly un-ignored in .butterbaseignore. */
6
+ export const HARDCODED_IGNORES = [
7
+ '.git/',
8
+ 'node_modules/',
9
+ 'dist/',
10
+ '.next/',
11
+ '.turbo/',
12
+ '.DS_Store',
13
+ // The bound-app config and its caches.
14
+ '.butterbase/',
15
+ ];
16
+ /**
17
+ * Load ignore rules from `root`.
18
+ *
19
+ * Precedence (highest wins): .butterbaseignore > .gitignore > HARDCODED_IGNORES.
20
+ * The `ignore` package treats later add()s as overriding earlier ones, and supports `!path`
21
+ * negations to un-ignore. So we add in order defaults → .gitignore → .butterbaseignore.
22
+ */
23
+ export async function loadIgnoreRules(root) {
24
+ const ig = ignore();
25
+ ig.add(HARDCODED_IGNORES);
26
+ const gitIgnore = path.join(root, '.gitignore');
27
+ if (await fs.pathExists(gitIgnore)) {
28
+ ig.add(await fs.readFile(gitIgnore, 'utf8'));
29
+ }
30
+ const bbIgnore = path.join(root, '.butterbaseignore');
31
+ if (await fs.pathExists(bbIgnore)) {
32
+ ig.add(await fs.readFile(bbIgnore, 'utf8'));
33
+ }
34
+ return ig;
35
+ }
36
+ /**
37
+ * Test whether a posix-style relative path is ignored.
38
+ * Pass directory paths with a trailing slash, e.g. `foo/`.
39
+ */
40
+ export function isIgnored(ig, posixRelPath) {
41
+ // ignore() panics on leading "/"; strip it defensively.
42
+ return ig.ignores(posixRelPath.replace(/^\/+/, ''));
43
+ }
44
+ //# sourceMappingURL=repo-ignore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo-ignore.js","sourceRoot":"","sources":["../../../src/lib/repo-ignore.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,MAAuB,MAAM,QAAQ,CAAC;AAE7C,8FAA8F;AAC9F,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,OAAO;IACP,eAAe;IACf,OAAO;IACP,QAAQ;IACR,SAAS;IACT,WAAW;IACX,uCAAuC;IACvC,cAAc;CACf,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAY;IAChD,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;IACpB,EAAE,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAE1B,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAChD,IAAI,MAAM,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACnC,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;IAC/C,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;IACtD,IAAI,MAAM,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAClC,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9C,CAAC;IAED,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,EAAU,EAAE,YAAoB;IACxD,wDAAwD;IACxD,OAAO,EAAE,CAAC,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;AACtD,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { FileEntry } from './repo-api.js';
2
+ import type { WalkedFile } from './repo-walk.js';
3
+ /**
4
+ * Read every file's bytes and compute its sha256. Returns FileEntry[] suitable for
5
+ * /repo/snapshots/prepare. Caller is responsible for any progress UX — this is a tight loop.
6
+ *
7
+ * Concurrency is intentionally serial: a typical repo is small (100 MB cap) and parallel
8
+ * file reads on cold disk are not necessarily faster. Tighten later if profiling shows
9
+ * a hot path.
10
+ */
11
+ export declare function buildManifest(files: WalkedFile[]): Promise<FileEntry[]>;
12
+ //# sourceMappingURL=repo-manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo-manifest.d.ts","sourceRoot":"","sources":["../../../src/lib/repo-manifest.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD;;;;;;;GAOG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAa7E"}
@@ -0,0 +1,25 @@
1
+ import fs from 'fs-extra';
2
+ import { createHash } from 'crypto';
3
+ /**
4
+ * Read every file's bytes and compute its sha256. Returns FileEntry[] suitable for
5
+ * /repo/snapshots/prepare. Caller is responsible for any progress UX — this is a tight loop.
6
+ *
7
+ * Concurrency is intentionally serial: a typical repo is small (100 MB cap) and parallel
8
+ * file reads on cold disk are not necessarily faster. Tighten later if profiling shows
9
+ * a hot path.
10
+ */
11
+ export async function buildManifest(files) {
12
+ const out = [];
13
+ for (const f of files) {
14
+ const buf = await fs.readFile(f.absPath);
15
+ const sha256 = createHash('sha256').update(buf).digest('hex');
16
+ if (buf.byteLength !== f.size) {
17
+ // The stat said one thing, the read returned another — likely a concurrent writer.
18
+ // Surface this rather than committing inconsistent metadata.
19
+ throw new Error(`File size changed during read: ${f.relPath} (stat=${f.size}, read=${buf.byteLength})`);
20
+ }
21
+ out.push({ path: f.relPath, sha256, size: buf.byteLength });
22
+ }
23
+ return out;
24
+ }
25
+ //# sourceMappingURL=repo-manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo-manifest.js","sourceRoot":"","sources":["../../../src/lib/repo-manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAIpC;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAmB;IACrD,MAAM,GAAG,GAAgB,EAAE,CAAC;IAC5B,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACzC,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC9D,IAAI,GAAG,CAAC,UAAU,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;YAC9B,mFAAmF;YACnF,6DAA6D;YAC7D,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC,OAAO,UAAU,CAAC,CAAC,IAAI,UAAU,GAAG,CAAC,UAAU,GAAG,CAAC,CAAC;QAC1G,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Mirror of the server's path validation (services/control-api/src/services/repo-manifest.ts).
3
+ * Keeping it duplicated rather than imported keeps the CLI free of a server import; the rules
4
+ * are simple enough that drift risk is low (and would surface as a 400 from prepare anyway).
5
+ */
6
+ export declare const REPO_MAX_PATH_BYTES = 4096;
7
+ export declare class PathError extends Error {
8
+ readonly code: string;
9
+ constructor(code: string, message: string);
10
+ }
11
+ export declare function validateRelativePath(p: string): void;
12
+ /** Convert a possibly-Windows path (`a\\b`) to forward-slash form for the manifest. */
13
+ export declare function toPosixRelative(rel: string): string;
14
+ //# sourceMappingURL=repo-paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo-paths.d.ts","sourceRoot":"","sources":["../../../src/lib/repo-paths.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,OAAO,CAAC;AAExC,qBAAa,SAAU,SAAQ,KAAK;aACN,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAI1D;AAED,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAapD;AAED,uFAAuF;AACvF,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEnD"}
@@ -0,0 +1,41 @@
1
+ // submodules/butterbase-oss/packages/cli/src/lib/repo-paths.ts
2
+ /**
3
+ * Mirror of the server's path validation (services/control-api/src/services/repo-manifest.ts).
4
+ * Keeping it duplicated rather than imported keeps the CLI free of a server import; the rules
5
+ * are simple enough that drift risk is low (and would surface as a 400 from prepare anyway).
6
+ */
7
+ export const REPO_MAX_PATH_BYTES = 4096;
8
+ export class PathError extends Error {
9
+ code;
10
+ constructor(code, message) {
11
+ super(message);
12
+ this.code = code;
13
+ this.name = 'PathError';
14
+ }
15
+ }
16
+ export function validateRelativePath(p) {
17
+ if (p.length === 0)
18
+ throw new PathError('repo_path_empty', 'Path is empty');
19
+ if (Buffer.byteLength(p, 'utf8') > REPO_MAX_PATH_BYTES) {
20
+ throw new PathError('repo_path_too_long', `Path exceeds ${REPO_MAX_PATH_BYTES} bytes: ${p}`);
21
+ }
22
+ if (p.startsWith('/'))
23
+ throw new PathError('repo_path_absolute', `Path is absolute: ${p}`);
24
+ if (p.includes('\\'))
25
+ throw new PathError('repo_path_backslash', `Path contains backslash: ${p}`);
26
+ if (p.includes('\0'))
27
+ throw new PathError('repo_path_null_byte', `Path contains null byte`);
28
+ for (const seg of p.split('/')) {
29
+ if (seg === '..')
30
+ throw new PathError('repo_path_traversal', `Path contains '..': ${p}`);
31
+ if (seg === '.')
32
+ throw new PathError('repo_path_dot_segment', `Path contains '.' segment: ${p}`);
33
+ if (seg.length === 0)
34
+ throw new PathError('repo_path_empty_segment', `Path has empty segment: ${p}`);
35
+ }
36
+ }
37
+ /** Convert a possibly-Windows path (`a\\b`) to forward-slash form for the manifest. */
38
+ export function toPosixRelative(rel) {
39
+ return rel.split(/[\\/]+/).join('/');
40
+ }
41
+ //# sourceMappingURL=repo-paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo-paths.js","sourceRoot":"","sources":["../../../src/lib/repo-paths.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAE/D;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,CAAC;AAExC,MAAM,OAAO,SAAU,SAAQ,KAAK;IACN;IAA5B,YAA4B,IAAY,EAAE,OAAe;QACvD,KAAK,CAAC,OAAO,CAAC,CAAC;QADW,SAAI,GAAJ,IAAI,CAAQ;QAEtC,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;IAC1B,CAAC;CACF;AAED,MAAM,UAAU,oBAAoB,CAAC,CAAS;IAC5C,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,iBAAiB,EAAE,eAAe,CAAC,CAAC;IAC5E,IAAI,MAAM,CAAC,UAAU,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,mBAAmB,EAAE,CAAC;QACvD,MAAM,IAAI,SAAS,CAAC,oBAAoB,EAAE,gBAAgB,mBAAmB,WAAW,CAAC,EAAE,CAAC,CAAC;IAC/F,CAAC;IACD,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,oBAAoB,EAAE,qBAAqB,CAAC,EAAE,CAAC,CAAC;IAC3F,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,qBAAqB,EAAE,4BAA4B,CAAC,EAAE,CAAC,CAAC;IAClG,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,qBAAqB,EAAE,yBAAyB,CAAC,CAAC;IAC5F,KAAK,MAAM,GAAG,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/B,IAAI,GAAG,KAAK,IAAI;YAAE,MAAM,IAAI,SAAS,CAAC,qBAAqB,EAAE,uBAAuB,CAAC,EAAE,CAAC,CAAC;QACzF,IAAI,GAAG,KAAK,GAAG;YAAE,MAAM,IAAI,SAAS,CAAC,uBAAuB,EAAE,8BAA8B,CAAC,EAAE,CAAC,CAAC;QACjG,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM,IAAI,SAAS,CAAC,yBAAyB,EAAE,2BAA2B,CAAC,EAAE,CAAC,CAAC;IACvG,CAAC;AACH,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,OAAO,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACvC,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { Ignore } from 'ignore';
2
+ export interface WalkedFile {
3
+ /** Posix-style relative path from `root`. */
4
+ relPath: string;
5
+ /** Absolute filesystem path. */
6
+ absPath: string;
7
+ size: number;
8
+ }
9
+ /**
10
+ * Async-iterate every non-ignored regular file under `root`.
11
+ * Skips symlinks. Validates each path through validateRelativePath; throws on bad ones.
12
+ */
13
+ export declare function walkRepo(root: string, ig: Ignore): AsyncGenerator<WalkedFile>;
14
+ //# sourceMappingURL=repo-walk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo-walk.d.ts","sourceRoot":"","sources":["../../../src/lib/repo-walk.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGrC,MAAM,WAAW,UAAU;IACzB,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,gCAAgC;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAuB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,cAAc,CAAC,UAAU,CAAC,CAuBpF"}
@@ -0,0 +1,36 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { isIgnored } from './repo-ignore.js';
4
+ import { toPosixRelative, validateRelativePath } from './repo-paths.js';
5
+ /**
6
+ * Async-iterate every non-ignored regular file under `root`.
7
+ * Skips symlinks. Validates each path through validateRelativePath; throws on bad ones.
8
+ */
9
+ export async function* walkRepo(root, ig) {
10
+ async function* recurse(dir) {
11
+ const entries = await fs.readdir(dir, { withFileTypes: true });
12
+ // Sorted for determinism (stable progress reporting, stable manifests in tests).
13
+ entries.sort((a, b) => a.name.localeCompare(b.name));
14
+ for (const entry of entries) {
15
+ const abs = path.join(dir, entry.name);
16
+ const rel = toPosixRelative(path.relative(root, abs));
17
+ // For directory ignore checks the `ignore` package expects a trailing slash.
18
+ const probe = entry.isDirectory() ? `${rel}/` : rel;
19
+ if (isIgnored(ig, probe))
20
+ continue;
21
+ if (entry.isSymbolicLink())
22
+ continue; // Skip symlinks — content semantics are ambiguous.
23
+ if (entry.isDirectory()) {
24
+ yield* recurse(abs);
25
+ continue;
26
+ }
27
+ if (!entry.isFile())
28
+ continue; // sockets, fifos, etc.
29
+ validateRelativePath(rel);
30
+ const st = await fs.stat(abs);
31
+ yield { relPath: rel, absPath: abs, size: st.size };
32
+ }
33
+ }
34
+ yield* recurse(root);
35
+ }
36
+ //# sourceMappingURL=repo-walk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo-walk.js","sourceRoot":"","sources":["../../../src/lib/repo-walk.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAUxE;;;GAGG;AACH,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,QAAQ,CAAC,IAAY,EAAE,EAAU;IACtD,KAAK,SAAS,CAAC,CAAC,OAAO,CAAC,GAAW;QACjC,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/D,iFAAiF;QACjF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACrD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACvC,MAAM,GAAG,GAAG,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;YACtD,6EAA6E;YAC7E,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;YACpD,IAAI,SAAS,CAAC,EAAE,EAAE,KAAK,CAAC;gBAAE,SAAS;YACnC,IAAI,KAAK,CAAC,cAAc,EAAE;gBAAE,SAAS,CAAE,mDAAmD;YAC1F,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxB,KAAK,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;gBACpB,SAAS;YACX,CAAC;YACD,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;gBAAE,SAAS,CAAE,uBAAuB;YACvD,oBAAoB,CAAC,GAAG,CAAC,CAAC;YAC1B,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC9B,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;QACtD,CAAC;IACH,CAAC;IACD,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AACvB,CAAC"}