Skip to content

Commit f0a1556

Browse files
unbuilt
1 parent b372fd5 commit f0a1556

3 files changed

Lines changed: 81 additions & 1 deletion

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@shipstatic/types",
3-
"version": "0.7.2",
3+
"version": "0.7.3",
44
"description": "Shared types for Shipstatic platform",
55
"type": "module",
66
"main": "./dist/index.js",

src/index.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,30 @@ export function isBlockedExtension(filename: string): boolean {
571571
return BLOCKED_EXTENSIONS.has(ext);
572572
}
573573

574+
// =============================================================================
575+
// UNBUILT PROJECT MARKERS
576+
// =============================================================================
577+
578+
/**
579+
* Directory names that indicate an unbuilt project was uploaded instead of build output.
580+
* Used for early detection in CLI, browser, and server validation.
581+
*/
582+
export const UNBUILT_PROJECT_MARKERS: ReadonlySet<string> = new Set([
583+
'node_modules',
584+
]);
585+
586+
/**
587+
* Check if a file path contains an unbuilt project marker directory.
588+
*
589+
* @example
590+
* hasUnbuiltMarker('node_modules/react/index.js') // true
591+
* hasUnbuiltMarker('dist/index.html') // false
592+
*/
593+
export function hasUnbuiltMarker(filePath: string): boolean {
594+
const segments = filePath.replace(/\\/g, '/').split('/').filter(Boolean);
595+
return segments.some(s => UNBUILT_PROJECT_MARKERS.has(s));
596+
}
597+
574598
// =============================================================================
575599
// COMMON RESPONSE PATTERNS
576600
// =============================================================================

tests/validation-constants.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { describe, it, expect } from 'vitest';
22
import {
33
BLOCKED_EXTENSIONS,
44
isBlockedExtension,
5+
UNBUILT_PROJECT_MARKERS,
6+
hasUnbuiltMarker,
57
FileValidationStatus,
68
LABEL_PATTERN,
79
LABEL_CONSTRAINTS,
@@ -118,6 +120,60 @@ describe('Validation Constants - @shipstatic/types', () => {
118120
});
119121
});
120122

123+
describe('UNBUILT_PROJECT_MARKERS', () => {
124+
it('should contain node_modules', () => {
125+
expect(UNBUILT_PROJECT_MARKERS.has('node_modules')).toBe(true);
126+
});
127+
128+
it('should NOT match partial names', () => {
129+
expect(UNBUILT_PROJECT_MARKERS.has('node')).toBe(false);
130+
expect(UNBUILT_PROJECT_MARKERS.has('modules')).toBe(false);
131+
});
132+
133+
it('should be case-sensitive', () => {
134+
expect(UNBUILT_PROJECT_MARKERS.has('Node_Modules')).toBe(false);
135+
expect(UNBUILT_PROJECT_MARKERS.has('NODE_MODULES')).toBe(false);
136+
});
137+
});
138+
139+
describe('hasUnbuiltMarker()', () => {
140+
it('should detect node_modules in paths', () => {
141+
expect(hasUnbuiltMarker('node_modules/react/index.js')).toBe(true);
142+
expect(hasUnbuiltMarker('project/node_modules/lodash/lodash.js')).toBe(true);
143+
});
144+
145+
it('should detect node_modules as a standalone segment', () => {
146+
expect(hasUnbuiltMarker('node_modules')).toBe(true);
147+
});
148+
149+
it('should handle backslash paths', () => {
150+
expect(hasUnbuiltMarker('project\\node_modules\\react\\index.js')).toBe(true);
151+
});
152+
153+
it('should return false for clean build output paths', () => {
154+
expect(hasUnbuiltMarker('dist/index.html')).toBe(false);
155+
expect(hasUnbuiltMarker('build/static/app.js')).toBe(false);
156+
expect(hasUnbuiltMarker('out/index.html')).toBe(false);
157+
expect(hasUnbuiltMarker('index.html')).toBe(false);
158+
});
159+
160+
it('should not match partial directory names', () => {
161+
expect(hasUnbuiltMarker('my_node_modules_backup/file.js')).toBe(false);
162+
expect(hasUnbuiltMarker('not_node_modules/file.js')).toBe(false);
163+
});
164+
165+
it('should be case-sensitive', () => {
166+
expect(hasUnbuiltMarker('Node_Modules/react/index.js')).toBe(false);
167+
expect(hasUnbuiltMarker('NODE_MODULES/react/index.js')).toBe(false);
168+
});
169+
170+
it('should handle edge cases', () => {
171+
expect(hasUnbuiltMarker('')).toBe(false);
172+
expect(hasUnbuiltMarker('/')).toBe(false);
173+
expect(hasUnbuiltMarker('//')).toBe(false);
174+
});
175+
});
176+
121177
describe('ConfigResponse', () => {
122178
it('should have correct shape with 3 fields', () => {
123179
const config: ConfigResponse = {

0 commit comments

Comments
 (0)