Repetitive structs removed for import file extensions (#6211)

* get rid of repetitive structs

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fmt

Signed-off-by: Jess Frazelle <github@jessfraz.com>

get rid of more

Signed-off-by: Jess Frazelle <github@jessfraz.com>

add more

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fix

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

await the shit

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

put it at the root

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix;es

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* kcl-language-server flake

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
Jess Frazelle
2025-04-10 13:57:12 -07:00
committed by GitHub
parent 970cf7f017
commit d33d399c31
17 changed files with 220 additions and 183 deletions

1
.gitignore vendored
View File

@ -26,6 +26,7 @@ yarn-error.log*
.idea .idea
.vscode .vscode
.helix .helix
result
# rust # rust
rust/target rust/target

41
flake.lock generated
View File

@ -1,6 +1,40 @@
{ {
"nodes": { "nodes": {
"naersk": {
"inputs": {
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1743800763,
"narHash": "sha256-YFKV+fxEpMgP5VsUcM6Il28lI0NlpM7+oB1XxbBAYCw=",
"owner": "nix-community",
"repo": "naersk",
"rev": "ed0232117731a4c19d3ee93aa0c382a8fe754b01",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "naersk",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": {
"lastModified": 1744096231,
"narHash": "sha256-kUfx3FKU1Etnua3EaKvpeuXs7zoFiAcli1gBwkPvGSs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b2b0718004cc9a5bca610326de0a82e6ea75920b",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1736320768, "lastModified": 1736320768,
"narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=", "narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=",
@ -16,7 +50,7 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_2": { "nixpkgs_3": {
"locked": { "locked": {
"lastModified": 1728538411, "lastModified": 1728538411,
"narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=", "narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=",
@ -34,13 +68,14 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"nixpkgs": "nixpkgs", "naersk": "naersk",
"nixpkgs": "nixpkgs_2",
"rust-overlay": "rust-overlay" "rust-overlay": "rust-overlay"
} }
}, },
"rust-overlay": { "rust-overlay": {
"inputs": { "inputs": {
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_3"
}, },
"locked": { "locked": {
"lastModified": 1736476219, "lastModified": 1736476219,

142
flake.nix
View File

@ -1,84 +1,104 @@
{ {
description = "modeling-app development environment"; description = "zoo.dev modeling-app";
# Flake inputs
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
rust-overlay.url = "github:oxalica/rust-overlay"; # A helper for Rust + Nix rust-overlay.url = "github:oxalica/rust-overlay";
naersk.url = "github:nix-community/naersk";
}; };
# Flake outputs outputs = {
outputs = { self, nixpkgs, rust-overlay }: self,
let nixpkgs,
# Overlays enable you to customize the Nixpkgs attribute set rust-overlay,
overlays = [ naersk,
# Makes a `rust-bin` attribute available in Nixpkgs }: let
(import rust-overlay) overlays = [
# Provides a `rustToolchain` attribute for Nixpkgs that we can use to (import rust-overlay)
# create a Rust environment (self: super: {
(self: super: { rustToolchain = super.rust-bin.stable.latest.default.override {
rustToolchain = super. rust-bin.stable.latest.default.override { targets = ["wasm32-unknown-unknown"];
targets = [ "wasm32-unknown-unknown" ]; extensions = ["rustfmt" "llvm-tools-preview" "rust-src"];
extensions = [ "rustfmt" "llvm-tools-preview" "rust-src" ]; };
})
(self: super: {
cargo-llvm-cov = super.cargo-llvm-cov.overrideAttrs (oa: {
doCheck = false;
doInstallCheck = false;
});
})
];
allSystems = [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
forAllSystems = f:
nixpkgs.lib.genAttrs allSystems (system:
f {
pkgs = import nixpkgs {
inherit overlays system;
config.allowBroken = true;
}; };
}) system = system;
(self: super: { });
cargo-llvm-cov = super.cargo-llvm-cov.overrideAttrs(oa: { in {
doCheck = false; doInstallCheck = false; devShells = forAllSystems ({pkgs, ...}: {
}); default = pkgs.mkShell {
}) packages =
]; (with pkgs; [
# Systems supported
allSystems = [
"x86_64-linux" # 64-bit Intel/AMD Linux
"aarch64-linux" # 64-bit ARM Linux
"x86_64-darwin" # 64-bit Intel macOS
"aarch64-darwin" # 64-bit ARM macOS
];
# Helper to provide system-specific attributes
forAllSystems = f: nixpkgs.lib.genAttrs allSystems (system: f {
pkgs = import nixpkgs { inherit overlays system; config.allowBroken = true; };
});
in
{
# Development environment output
devShells = forAllSystems ({ pkgs }: {
default = pkgs.mkShell {
# The Nix packages provided in the environment
packages = (with pkgs; [
# The package provided by our custom overlay. Includes cargo, Clippy, cargo-fmt,
# rustdoc, rustfmt, and other tools.
rustToolchain rustToolchain
cargo-llvm-cov cargo-llvm-cov
cargo-nextest cargo-nextest
just just
postgresql.lib postgresql.lib
openssl openssl
pkg-config pkg-config
nodejs_22 nodejs_22
yarn yarn
electron electron
playwright-driver.browsers playwright-driver.browsers
]) ++ pkgs.lib.optionals pkgs.stdenv.isDarwin (with pkgs; [ wasm-pack
python3Full
])
++ pkgs.lib.optionals pkgs.stdenv.isDarwin (with pkgs; [
libiconv libiconv
darwin.apple_sdk.frameworks.Security darwin.apple_sdk.frameworks.Security
]); ]);
TARGET_CC = "${pkgs.stdenv.cc}/bin/${pkgs.stdenv.cc.targetPrefix}cc"; TARGET_CC = "${pkgs.stdenv.cc}/bin/${pkgs.stdenv.cc.targetPrefix}cc";
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib"; LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
ELECTRON_OVERRIDE_DIST_PATH = "${pkgs.electron}/bin/"; ELECTRON_OVERRIDE_DIST_PATH = "${pkgs.electron}/bin/";
PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS = true; PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS = true;
PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH = "${pkgs.playwright-driver.browsers}/chromium-1091/chrome-linux/chrome"; PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH = "${pkgs.playwright-driver.browsers}/chromium-1091/chrome-linux/chrome";
PLAYWRIGHT_BROWSERS_PATH = "${pkgs.playwright-driver.browsers}"; PLAYWRIGHT_BROWSERS_PATH = "${pkgs.playwright-driver.browsers}";
NODE_ENV = "development"; NODE_ENV = "development";
}; };
}); });
};
packages = forAllSystems ({
pkgs,
system,
}: let
naersk-lib = pkgs.callPackage naersk {
cargo = pkgs.rustToolchain;
rustc = pkgs.rustToolchain;
};
in {
kcl-language-server = naersk-lib.buildPackage {
pname = "kcl-language-server";
version = "0.1.0";
release = true;
src = ./rust;
cargoBuildOptions = opt: opt ++ ["-p" "kcl-language-server"];
buildInputs = [pkgs.openssl pkgs.pkg-config];
};
default = self.packages.${system}.kcl-language-server;
});
};
} }

View File

@ -103,7 +103,7 @@ tokio-tungstenite = { version = "0.24.0", features = [
tower-lsp = { workspace = true, features = ["proposed", "default"] } tower-lsp = { workspace = true, features = ["proposed", "default"] }
[features] [features]
default = ["engine"] default = ["cli", "engine"]
cli = ["dep:clap", "kittycad/clap"] cli = ["dep:clap", "kittycad/clap"]
dhat-heap = ["dep:dhat"] dhat-heap = ["dep:dhat"]
# For the lsp server, when run with stdout for rpc we want to disable println. # For the lsp server, when run with stdout for rpc we want to disable println.

View File

@ -20,7 +20,6 @@ pub(crate) const SETTINGS_UNIT_ANGLE: &str = "defaultAngleUnit";
pub(super) const NO_PRELUDE: &str = "no_std"; pub(super) const NO_PRELUDE: &str = "no_std";
pub(super) const IMPORT_FORMAT: &str = "format"; pub(super) const IMPORT_FORMAT: &str = "format";
pub(super) const IMPORT_FORMAT_VALUES: [&str; 9] = ["fbx", "gltf", "glb", "obj", "ply", "sldprt", "stp", "step", "stl"];
pub(super) const IMPORT_COORDS: &str = "coords"; pub(super) const IMPORT_COORDS: &str = "coords";
pub(super) const IMPORT_COORDS_VALUES: [(&str, &System); 3] = pub(super) const IMPORT_COORDS_VALUES: [(&str, &System); 3] =
[("zoo", KITTYCAD), ("opengl", OPENGL), ("vulkan", VULKAN)]; [("zoo", KITTYCAD), ("opengl", OPENGL), ("vulkan", VULKAN)];

View File

@ -173,7 +173,7 @@ pub(super) fn format_from_annotations(
KclError::Semantic(KclErrorDetails { KclError::Semantic(KclErrorDetails {
message: format!( message: format!(
"Unknown format for import, expected one of: {}", "Unknown format for import, expected one of: {}",
annotations::IMPORT_FORMAT_VALUES.join(", ") crate::IMPORT_FILE_EXTENSIONS.join(", ")
), ),
source_ranges: vec![p.as_source_range()], source_ranges: vec![p.as_source_range()],
}) })

View File

@ -131,11 +131,36 @@ pub mod pretty {
pub use crate::{parsing::token::NumericSuffix, unparser::format_number}; pub use crate::{parsing::token::NumericSuffix, unparser::format_number};
} }
#[cfg(feature = "cli")]
use clap::ValueEnum;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[allow(unused_imports)] #[allow(unused_imports)]
use crate::log::{log, logln}; use crate::log::{log, logln};
lazy_static::lazy_static! {
pub static ref IMPORT_FILE_EXTENSIONS: Vec<String> = {
let mut import_file_extensions = vec!["stp".to_string(), "glb".to_string(), "fbxb".to_string()];
#[cfg(feature = "cli")]
let named_extensions = kittycad::types::FileImportFormat::value_variants()
.iter()
.map(|x| format!("{}", x))
.collect::<Vec<String>>();
#[cfg(not(feature = "cli"))]
let named_extensions = vec![]; // We don't really need this outside of the CLI.
// Add all the default import formats.
import_file_extensions.extend_from_slice(&named_extensions);
import_file_extensions
};
pub static ref RELEVANT_FILE_EXTENSIONS: Vec<String> = {
let mut relevant_extensions = IMPORT_FILE_EXTENSIONS.clone();
relevant_extensions.push("kcl".to_string());
relevant_extensions
};
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Program { pub struct Program {
#[serde(flatten)] #[serde(flatten)]

View File

@ -35,7 +35,7 @@ use crate::{
token::{Token, TokenSlice, TokenType}, token::{Token, TokenSlice, TokenType},
PIPE_OPERATOR, PIPE_SUBSTITUTION_OPERATOR, PIPE_OPERATOR, PIPE_SUBSTITUTION_OPERATOR,
}, },
SourceRange, SourceRange, IMPORT_FILE_EXTENSIONS,
}; };
thread_local! { thread_local! {
@ -1843,8 +1843,6 @@ fn import_stmt(i: &mut TokenSlice) -> PResult<BoxNode<ImportStatement>> {
)) ))
} }
const FOREIGN_IMPORT_EXTENSIONS: [&str; 8] = ["fbx", "gltf", "glb", "obj", "ply", "sldprt", "step", "stl"];
/// Validates the path string in an `import` statement. /// Validates the path string in an `import` statement.
/// ///
/// `var_name` is `true` if the path will be used as a variable name. /// `var_name` is `true` if the path will be used as a variable name.
@ -1909,12 +1907,11 @@ fn validate_path_string(path_string: String, var_name: bool, path_range: SourceR
ImportPath::Std { path: segments } ImportPath::Std { path: segments }
} else if path_string.contains('.') { } else if path_string.contains('.') {
// TODO should allow other extensions if there is a format attribute. let extn = std::path::Path::new(&path_string).extension().unwrap_or_default();
let extn = &path_string[path_string.rfind('.').unwrap() + 1..]; if !IMPORT_FILE_EXTENSIONS.contains(&extn.to_string_lossy().to_string()) {
if !FOREIGN_IMPORT_EXTENSIONS.contains(&extn) {
ParseContext::warn(CompilationError::err( ParseContext::warn(CompilationError::err(
path_range, path_range,
format!("unsupported import path format. KCL files can be imported from the current project, CAD files with the following formats are supported: {}", FOREIGN_IMPORT_EXTENSIONS.join(", ")), format!("unsupported import path format. KCL files can be imported from the current project, CAD files with the following formats are supported: {}", IMPORT_FILE_EXTENSIONS.join(", ")),
)) ))
} }
ImportPath::Foreign { path: path_string } ImportPath::Foreign { path: path_string }
@ -1922,7 +1919,7 @@ fn validate_path_string(path_string: String, var_name: bool, path_range: SourceR
return Err(ErrMode::Cut( return Err(ErrMode::Cut(
CompilationError::fatal( CompilationError::fatal(
path_range, path_range,
format!("unsupported import path format. KCL files can be imported from the current project, CAD files with the following formats are supported: {}", FOREIGN_IMPORT_EXTENSIONS.join(", ")), format!("unsupported import path format. KCL files can be imported from the current project, CAD files with the following formats are supported: {}", IMPORT_FILE_EXTENSIONS.join(", ")),
) )
.into(), .into(),
)); ));

View File

@ -1,8 +1,5 @@
use std::fmt::Write; use std::fmt::Write;
#[cfg(feature = "cli")]
use clap::ValueEnum;
use crate::parsing::{ use crate::parsing::{
ast::types::{ ast::types::{
Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem,
@ -867,29 +864,6 @@ impl Parameter {
} }
} }
lazy_static::lazy_static! {
pub static ref IMPORT_FILE_EXTENSIONS: Vec<String> = {
let mut import_file_extensions = vec!["stp".to_string(), "glb".to_string(), "fbxb".to_string()];
#[cfg(feature = "cli")]
let named_extensions = kittycad::types::FileImportFormat::value_variants()
.iter()
.map(|x| format!("{}", x))
.collect::<Vec<String>>();
#[cfg(not(feature = "cli"))]
let named_extensions = vec![]; // We don't really need this outside of the CLI.
// Add all the default import formats.
import_file_extensions.extend_from_slice(&named_extensions);
import_file_extensions
};
pub static ref RELEVANT_EXTENSIONS: Vec<String> = {
let mut relevant_extensions = IMPORT_FILE_EXTENSIONS.clone();
relevant_extensions.push("kcl".to_string());
relevant_extensions
};
}
/// Collect all the kcl (and other relevant) files in a directory, recursively. /// Collect all the kcl (and other relevant) files in a directory, recursively.
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
#[async_recursion::async_recursion] #[async_recursion::async_recursion]
@ -909,7 +883,7 @@ pub async fn walk_dir(dir: &std::path::PathBuf) -> Result<Vec<std::path::PathBuf
files.extend(walk_dir(&path).await?); files.extend(walk_dir(&path).await?);
} else if path } else if path
.extension() .extension()
.is_some_and(|ext| RELEVANT_EXTENSIONS.contains(&ext.to_string_lossy().to_string())) .is_some_and(|ext| crate::RELEVANT_FILE_EXTENSIONS.contains(&ext.to_string_lossy().to_string()))
{ {
files.push(path); files.push(path);
} }

View File

@ -292,3 +292,22 @@ pub fn get_kcl_version() -> String {
kcl_lib::version().to_string() kcl_lib::version().to_string()
} }
/// Get the allowed import file extensions.
#[wasm_bindgen]
pub fn import_file_extensions() -> Result<Vec<String>, String> {
console_error_panic_hook::set_once();
Ok(kcl_lib::IMPORT_FILE_EXTENSIONS.iter().map(|s| s.to_string()).collect())
}
/// Get the allowed relevant file extensions (imports + kcl).
#[wasm_bindgen]
pub fn relevant_file_extensions() -> Result<Vec<String>, String> {
console_error_panic_hook::set_once();
Ok(kcl_lib::RELEVANT_FILE_EXTENSIONS
.iter()
.map(|s| s.to_string())
.collect::<Vec<String>>())
}

12
src/lang/wasmUtils.ts Normal file
View File

@ -0,0 +1,12 @@
import {
import_file_extensions,
relevant_file_extensions,
} from '@rust/kcl-wasm-lib/pkg/kcl_wasm_lib'
export function importFileExtensions(): string[] {
return import_file_extensions()
}
export function relevantFileExtensions(): string[] {
return relevant_file_extensions()
}

View File

@ -1,5 +1,4 @@
import type { Models } from '@kittycad/lib/dist/types/src' import type { Models } from '@kittycad/lib/dist/types/src'
import type { FileImportFormat_type } from '@kittycad/lib/dist/types/src/models'
import type { UnitAngle, UnitLength } from '@rust/kcl-lib/bindings/ModelingCmd' import type { UnitAngle, UnitLength } from '@rust/kcl-lib/bindings/ModelingCmd'
@ -36,29 +35,6 @@ export const PROJECT_IMAGE_NAME = `thumbnail.png`
export const FILE_PERSIST_KEY = `${PROJECT_FOLDER}-last-opened` as const export const FILE_PERSIST_KEY = `${PROJECT_FOLDER}-last-opened` as const
/** The default name given to new kcl files in a project */ /** The default name given to new kcl files in a project */
export const DEFAULT_FILE_NAME = 'Untitled' export const DEFAULT_FILE_NAME = 'Untitled'
/** The file endings that will appear in
* the file explorer if found in a project directory */
// TODO: make stp part of this enum as an alias to step
// TODO: make glb part of this enum as it is in fact supported
export type NativeFileType = 'kcl'
export type RelevantFileType =
| FileImportFormat_type
| NativeFileType
| 'stp'
| 'glb'
export const NATIVE_FILE_TYPE: NativeFileType = 'kcl'
export const RELEVANT_FILE_TYPES: RelevantFileType[] = [
'kcl',
'fbx',
'gltf',
'glb',
'obj',
'ply',
'sldprt',
'stp',
'step',
'stl',
] as const
/** The default name for a tutorial project */ /** The default name for a tutorial project */
export const ONBOARDING_PROJECT_NAME = 'Tutorial Project $nn' export const ONBOARDING_PROJECT_NAME = 'Tutorial Project $nn'
/** /**

View File

@ -2,9 +2,14 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { Configuration } from '@rust/kcl-lib/bindings/Configuration' import type { Configuration } from '@rust/kcl-lib/bindings/Configuration'
import { isRelevantFile, listProjects } from '@src/lib/desktop' import { initPromise } from '@src/lang/wasm'
import { listProjects } from '@src/lib/desktop'
import type { DeepPartial } from '@src/lib/types' import type { DeepPartial } from '@src/lib/types'
beforeAll(async () => {
await initPromise
})
// Mock the electron window global // Mock the electron window global
const mockElectron = { const mockElectron = {
readdir: vi.fn(), readdir: vi.fn(),
@ -112,41 +117,6 @@ describe('desktop utilities', () => {
mockElectron.kittycad.mockResolvedValue({}) mockElectron.kittycad.mockResolvedValue({})
}) })
describe('isRelevantFile', () => {
it('finds supported extension files relevant', () => {
expect(isRelevantFile('part.kcl')).toEqual(true)
expect(isRelevantFile('part.fbx')).toEqual(true)
expect(isRelevantFile('part.gltf')).toEqual(true)
expect(isRelevantFile('part.glb')).toEqual(true)
expect(isRelevantFile('part.obj')).toEqual(true)
expect(isRelevantFile('part.ply')).toEqual(true)
expect(isRelevantFile('part.sldprt')).toEqual(true)
expect(isRelevantFile('part.stp')).toEqual(true)
expect(isRelevantFile('part.step')).toEqual(true)
expect(isRelevantFile('part.stl')).toEqual(true)
})
// TODO: we should be lowercasing the extension here to check. .sldprt or .SLDPRT should be supported
// But the api doesn't allow it today, so revisit this and the tests once this is done
it('finds (now) supported uppercase extension files *not* relevant', () => {
expect(isRelevantFile('part.KCL')).toEqual(false)
expect(isRelevantFile('part.FBX')).toEqual(false)
expect(isRelevantFile('part.GLTF')).toEqual(false)
expect(isRelevantFile('part.GLB')).toEqual(false)
expect(isRelevantFile('part.OBJ')).toEqual(false)
expect(isRelevantFile('part.PLY')).toEqual(false)
expect(isRelevantFile('part.SLDPRT')).toEqual(false)
expect(isRelevantFile('part.STP')).toEqual(false)
expect(isRelevantFile('part.STEP')).toEqual(false)
expect(isRelevantFile('part.STL')).toEqual(false)
})
it("doesn't find .docx or .SLDASM relevant", () => {
expect(isRelevantFile('paper.docx')).toEqual(false)
expect(isRelevantFile('assembly.SLDASM')).toEqual(false)
})
})
describe('listProjects', () => { describe('listProjects', () => {
it('does not list .git directories', async () => { it('does not list .git directories', async () => {
const projects = await listProjects(mockConfig) const projects = await listProjects(mockConfig)

View File

@ -10,13 +10,13 @@ import {
parseAppSettings, parseAppSettings,
parseProjectSettings, parseProjectSettings,
} from '@src/lang/wasm' } from '@src/lang/wasm'
import { relevantFileExtensions } from '@src/lang/wasmUtils'
import { import {
DEFAULT_DEFAULT_LENGTH_UNIT, DEFAULT_DEFAULT_LENGTH_UNIT,
PROJECT_ENTRYPOINT, PROJECT_ENTRYPOINT,
PROJECT_FOLDER, PROJECT_FOLDER,
PROJECT_IMAGE_NAME, PROJECT_IMAGE_NAME,
PROJECT_SETTINGS_FILE_NAME, PROJECT_SETTINGS_FILE_NAME,
RELEVANT_FILE_TYPES,
SETTINGS_FILE_NAME, SETTINGS_FILE_NAME,
TELEMETRY_FILE_NAME, TELEMETRY_FILE_NAME,
TELEMETRY_RAW_FILE_NAME, TELEMETRY_RAW_FILE_NAME,
@ -201,15 +201,13 @@ export async function listProjects(
return projects return projects
} }
// TODO: we should be lowercasing the extension here to check. .sldprt or .SLDPRT should be supported
// But the api doesn't allow it today, so revisit this and the tests once this is done
export const isRelevantFile = (filename: string): boolean =>
RELEVANT_FILE_TYPES.some((ext) => filename.endsWith('.' + ext))
const collectAllFilesRecursiveFrom = async ( const collectAllFilesRecursiveFrom = async (
path: string, path: string,
canReadWritePath: boolean canReadWritePath: boolean
) => { ) => {
const RELEVANT_FILE_EXTENSIONS = relevantFileExtensions()
const isRelevantFile = (filename: string): boolean =>
RELEVANT_FILE_EXTENSIONS.some((ext) => filename.endsWith('.' + ext))
// Make sure the filesystem object exists. // Make sure the filesystem object exists.
try { try {
await window.electron.stat(path) await window.electron.stat(path)

View File

@ -3,8 +3,13 @@ import os from 'os'
import path from 'path' import path from 'path'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { initPromise } from '@src/lang/wasm'
import getCurrentProjectFile from '@src/lib/getCurrentProjectFile' import getCurrentProjectFile from '@src/lib/getCurrentProjectFile'
beforeAll(async () => {
await initPromise
})
describe('getCurrentProjectFile', () => { describe('getCurrentProjectFile', () => {
test('with explicit open file with space (URL encoded)', async () => { test('with explicit open file with space (URL encoded)', async () => {
const name = `kittycad-modeling-projects-${uuidv4()}` const name = `kittycad-modeling-projects-${uuidv4()}`

View File

@ -1,17 +1,12 @@
import {
importFileExtensions,
relevantFileExtensions,
} from '@src/lang/wasmUtils'
import type { Stats } from 'fs' import type { Stats } from 'fs'
import * as fs from 'fs/promises' import * as fs from 'fs/promises'
import * as path from 'path' import * as path from 'path'
import { import { PROJECT_ENTRYPOINT } from '@src/lib/constants'
NATIVE_FILE_TYPE,
PROJECT_ENTRYPOINT,
RELEVANT_FILE_TYPES,
type RelevantFileType,
} from '@src/lib/constants'
const shouldWrapExtension = (extension: string) =>
RELEVANT_FILE_TYPES.includes(extension as RelevantFileType) &&
extension !== NATIVE_FILE_TYPE
/// Get the current project file from the path. /// Get the current project file from the path.
/// This is used for double-clicking on a file in the file explorer, /// This is used for double-clicking on a file in the file explorer,
@ -19,6 +14,12 @@ const shouldWrapExtension = (extension: string) =>
export default async function getCurrentProjectFile( export default async function getCurrentProjectFile(
pathString: string pathString: string
): Promise<string | Error> { ): Promise<string | Error> {
// Extract the values into an array
const allFileImportFormats: string[] = importFileExtensions()
const relevantExtensions: string[] = relevantFileExtensions()
const shouldWrapExtension = (extension: string) =>
allFileImportFormats.includes(extension)
// Fix for "." path, which is the current directory. // Fix for "." path, which is the current directory.
let sourcePath = pathString === '.' ? process.cwd() : pathString let sourcePath = pathString === '.' ? process.cwd() : pathString
@ -71,12 +72,9 @@ export default async function getCurrentProjectFile(
// Check if the extension on what we are trying to open is a relevant file type. // Check if the extension on what we are trying to open is a relevant file type.
const extension = path.extname(sourcePath).slice(1).toLowerCase() const extension = path.extname(sourcePath).slice(1).toLowerCase()
if ( if (!relevantExtensions.includes(extension) && extension !== 'toml') {
!RELEVANT_FILE_TYPES.includes(extension as RelevantFileType) &&
extension !== 'toml'
) {
return new Error( return new Error(
`File type (${extension}) cannot be opened with this app: '${sourcePath}', try opening one of the following file types: ${RELEVANT_FILE_TYPES.join( `File type (${extension}) cannot be opened with this app: '${sourcePath}', try opening one of the following file types: ${relevantExtensions.join(
', ' ', '
)}` )}`
) )

View File

@ -15,6 +15,7 @@ import type {
format_number as FormatNumber, format_number as FormatNumber,
get_kcl_version as GetKclVersion, get_kcl_version as GetKclVersion,
get_tangential_arc_to_info as GetTangentialArcToInfo, get_tangential_arc_to_info as GetTangentialArcToInfo,
import_file_extensions as ImportFileExtensions,
is_kcl_empty_or_only_settings as IsKclEmptyOrOnlySettings, is_kcl_empty_or_only_settings as IsKclEmptyOrOnlySettings,
is_points_ccw as IsPointsCcw, is_points_ccw as IsPointsCcw,
kcl_lint as KclLint, kcl_lint as KclLint,
@ -23,6 +24,7 @@ import type {
parse_project_settings as ParseProjectSettings, parse_project_settings as ParseProjectSettings,
parse_wasm as ParseWasm, parse_wasm as ParseWasm,
recast_wasm as RecastWasm, recast_wasm as RecastWasm,
relevant_file_extensions as RelevantFileExtensions,
serialize_configuration as SerializeConfiguration, serialize_configuration as SerializeConfiguration,
serialize_project_configuration as SerializeProjectConfiguration, serialize_project_configuration as SerializeProjectConfiguration,
} from '@rust/kcl-wasm-lib/pkg/kcl_wasm_lib' } from '@rust/kcl-wasm-lib/pkg/kcl_wasm_lib'
@ -111,3 +113,9 @@ export const serialize_project_configuration: typeof SerializeProjectConfigurati
(...args) => { (...args) => {
return getModule().serialize_project_configuration(...args) return getModule().serialize_project_configuration(...args)
} }
export const import_file_extensions: typeof ImportFileExtensions = () => {
return getModule().import_file_extensions()
}
export const relevant_file_extensions: typeof RelevantFileExtensions = () => {
return getModule().relevant_file_extensions()
}