gitspace 0.2.0-rc.4 → 0.2.0-rc.6

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.
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Workspace script execution with phase tracking
3
+ *
4
+ * Consolidates the logic for running pre/setup/select scripts with
5
+ * proper error handling and phase identification.
6
+ */
7
+
8
+ import { runScriptsInTerminal, type RunScriptsOptions } from './run-scripts';
9
+ import { hasSetupBeenRun, markSetupComplete } from './workspace-state';
10
+ import { getScriptsPhaseDir, readProjectConfig } from '../core/config';
11
+ import { getProjectSecrets } from './secrets';
12
+
13
+ export type ScriptPhase = 'pre' | 'setup' | 'select';
14
+
15
+ export interface RunWorkspaceScriptsOptions {
16
+ projectName: string;
17
+ workspacePath: string;
18
+ workspaceName: string;
19
+ repository: string;
20
+ /** If true, scripts can prompt for input. If false (default), stdin is closed. */
21
+ interactive?: boolean;
22
+ }
23
+
24
+ export type RunWorkspaceScriptsResult =
25
+ | { success: true }
26
+ | { success: false; phase: ScriptPhase; error: string };
27
+
28
+ /**
29
+ * Run workspace scripts based on setup state.
30
+ *
31
+ * - If setup has already been run: runs select scripts only
32
+ * - If first time: runs pre scripts, then setup scripts, then marks setup complete
33
+ *
34
+ * Returns success or failure with the specific phase that failed.
35
+ */
36
+ export async function runWorkspaceScripts(
37
+ options: RunWorkspaceScriptsOptions
38
+ ): Promise<RunWorkspaceScriptsResult> {
39
+ const { projectName, workspacePath, workspaceName, repository, interactive = false } = options;
40
+
41
+ // Read project config for bundle values and secrets
42
+ const config = readProjectConfig(projectName);
43
+
44
+ // Build script options
45
+ const scriptOptions: RunScriptsOptions = {
46
+ bundleValues: config.bundleValues,
47
+ nonInteractive: !interactive,
48
+ };
49
+
50
+ // Fetch secrets from OS keychain if we have secret keys
51
+ if (config.bundleSecretKeys && config.bundleSecretKeys.length > 0) {
52
+ scriptOptions.bundleSecrets = await getProjectSecrets(projectName, config.bundleSecretKeys);
53
+ }
54
+
55
+ // Check if setup has been run for this workspace
56
+ const setupAlreadyRun = hasSetupBeenRun(workspacePath);
57
+
58
+ if (setupAlreadyRun) {
59
+ // Run select scripts for existing workspace
60
+ const selectScriptsDir = getScriptsPhaseDir(projectName, 'select');
61
+ try {
62
+ await runScriptsInTerminal(selectScriptsDir, workspacePath, workspaceName, repository, scriptOptions);
63
+ return { success: true };
64
+ } catch (error) {
65
+ const message = error instanceof Error ? error.message : String(error);
66
+ return { success: false, phase: 'select', error: message };
67
+ }
68
+ } else {
69
+ // First time accessing this workspace - run pre scripts, then setup scripts
70
+ let preScriptsSucceeded = false;
71
+
72
+ try {
73
+ const preScriptsDir = getScriptsPhaseDir(projectName, 'pre');
74
+ await runScriptsInTerminal(preScriptsDir, workspacePath, workspaceName, repository, scriptOptions);
75
+ preScriptsSucceeded = true;
76
+
77
+ const setupScriptsDir = getScriptsPhaseDir(projectName, 'setup');
78
+ await runScriptsInTerminal(setupScriptsDir, workspacePath, workspaceName, repository, scriptOptions);
79
+
80
+ // Only mark complete if both phases succeeded
81
+ markSetupComplete(workspacePath);
82
+ return { success: true };
83
+ } catch (error) {
84
+ const phase: ScriptPhase = preScriptsSucceeded ? 'setup' : 'pre';
85
+ const message = error instanceof Error ? error.message : String(error);
86
+ return { success: false, phase, error: message };
87
+ }
88
+ }
89
+ }
@@ -83,6 +83,70 @@ export function isValidWorkspaceName(name: string): boolean {
83
83
  return true;
84
84
  }
85
85
 
86
+ /**
87
+ * Validate a git branch name according to git-check-ref-format rules
88
+ * See: https://git-scm.com/docs/git-check-ref-format
89
+ *
90
+ * @param name Branch name to validate
91
+ * @returns true if valid, false otherwise
92
+ */
93
+ export function isValidBranchName(name: string): boolean {
94
+ if (!name || name.length === 0) {
95
+ return false;
96
+ }
97
+
98
+ // Cannot have two consecutive dots
99
+ if (name.includes('..')) {
100
+ return false;
101
+ }
102
+
103
+ // Cannot have ASCII control characters, space, tilde, caret, colon,
104
+ // question mark, asterisk, open bracket, or backslash
105
+ if (/[\x00-\x1f\x7f ~^:?*\[\\]/.test(name)) {
106
+ return false;
107
+ }
108
+
109
+ // Cannot start or end with a slash, or have consecutive slashes
110
+ if (name.startsWith('/') || name.endsWith('/') || name.includes('//')) {
111
+ return false;
112
+ }
113
+
114
+ // Cannot end with a dot
115
+ if (name.endsWith('.')) {
116
+ return false;
117
+ }
118
+
119
+ // Cannot contain @{
120
+ if (name.includes('@{')) {
121
+ return false;
122
+ }
123
+
124
+ // Cannot be exactly @
125
+ if (name === '@') {
126
+ return false;
127
+ }
128
+
129
+ // Cannot end with .lock
130
+ if (name.endsWith('.lock')) {
131
+ return false;
132
+ }
133
+
134
+ // Cannot start with a dash
135
+ if (name.startsWith('-')) {
136
+ return false;
137
+ }
138
+
139
+ // Each component cannot start with a dot
140
+ const components = name.split('/');
141
+ for (const component of components) {
142
+ if (component.startsWith('.') || component.length === 0) {
143
+ return false;
144
+ }
145
+ }
146
+
147
+ return true;
148
+ }
149
+
86
150
  /**
87
151
  * Extract repository name from owner/repo format
88
152
  *
@@ -3,7 +3,7 @@
3
3
  * Tracks whether setup commands have been run for a workspace
4
4
  */
5
5
 
6
- import { existsSync, writeFileSync } from 'fs';
6
+ import { existsSync, writeFileSync, unlinkSync } from 'fs';
7
7
  import { join } from 'path';
8
8
  import { SpacesError } from '../types/errors.js';
9
9
 
@@ -45,3 +45,19 @@ export function markSetupComplete(workspacePath: string): void {
45
45
  );
46
46
  }
47
47
  }
48
+
49
+ /**
50
+ * Clear the setup marker for a workspace (for testing)
51
+ * @param workspacePath Absolute path to the workspace directory
52
+ */
53
+ export function clearSetupMarker(workspacePath: string): void {
54
+ const markerPath = join(workspacePath, SETUP_MARKER_FILE);
55
+
56
+ try {
57
+ if (existsSync(markerPath)) {
58
+ unlinkSync(markerPath);
59
+ }
60
+ } catch {
61
+ // Ignore errors - this is just for testing
62
+ }
63
+ }
@@ -0,0 +1,2 @@
1
+ /** Generated at build time - see build script */
2
+ export const VERSION: string;
@@ -1,25 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(bun test:*)",
5
- "Bash(xargs ls:*)",
6
- "Bash(bun pm:*)",
7
- "Bash(node -e:*)",
8
- "Bash(bun run typecheck:*)",
9
- "Bash(bun -e:*)",
10
- "Bash(bun:*)",
11
- "Bash(xargs:*)",
12
- "Bash(timeout 2 bash -c 'echo \"\"invalid-base64\"\" | bun src/index.ts access add \"\"not-valid-base64-data\"\" 2>&1')",
13
- "Bash(wc:*)",
14
- "WebFetch(domain:ghostty.org)",
15
- "Bash(xxd:*)",
16
- "WebFetch(domain:ast-grep.github.io)",
17
- "WebFetch(domain:developers.cloudflare.com)",
18
- "WebFetch(domain:gitspace.sh)",
19
- "Bash(/Users/bradleat/spaces/spaces/workspaces/remote-space/dist/gssh-darwin-arm64:*)",
20
- "Bash(npx wrangler pages deploy:*)",
21
- "Bash(git add:*)",
22
- "Bash(git commit:*)"
23
- ]
24
- }
25
- }