Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/typegpu-cli/src/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as p from '@clack/prompts';

import { pmFromUserAgent, pmInstall } from './utils/pm.ts';
import { cancelExit, confirmStep, rgbText } from './utils/prompts.ts';
import { copyTemplate, prepareDirectory } from './utils/files.ts';
import { scaffoldProject, prepareDirectory } from './utils/files.ts';
import { getPackageName, getProjectDirectory } from './utils/inputs.ts';
import { detect, resolveCommand } from 'package-manager-detector';

Expand Down Expand Up @@ -40,7 +40,7 @@ export async function createProject(cwd: string) {
'../templates',
`template-${projectTemplate}`,
);
copyTemplate(templateDir, root, packageName);
await scaffoldProject(templateDir, root, packageName);

p.log.success(`Scaffolded project at ${projectDir}.`);

Expand Down
21 changes: 12 additions & 9 deletions packages/typegpu-cli/src/enhance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@ import * as p from '@clack/prompts';
import { detect, type Agent } from 'package-manager-detector';
import { type } from 'arktype';

import { PackageJsonWithDepsSchema, type PackageJsonWithDeps } from './utils/types.ts';
import { PackageJsonSchema, type PackageJson } from './utils/types.ts';
import { pmInstall } from './utils/pm.ts';
import { cancelExit, confirmStep, failAndExit, rgbText } from './utils/prompts.ts';
import { ensureWebgpuTypes } from './steps/webgpu-types.ts';
import { ensureTypegpu } from './steps/typegpu.ts';
import { ensureVite } from './steps/vite.ts';
import { askForWebgpuTypes } from './steps/webgpu-types.ts';
import { askForPkgs, ensureTypegpu } from './steps/typegpu.ts';
import { askForVite } from './steps/vite.ts';

const PROJECT_KINDS = [{ value: 'vite', label: rgbText('Vite', 175, 105, 245) }];

async function runViteFlow(cwd: string, pm: Agent, pkg: PackageJsonWithDeps) {
await ensureWebgpuTypes(cwd, pm, pkg);
await ensureVite(cwd, pm, pkg);
await ensureTypegpu(pm, pkg);
async function runViteFlow(cwd: string, pm: Agent, pkg: PackageJson) {
await askForWebgpuTypes(cwd, pm, pkg);
await askForVite(cwd, pm, pkg);
if (!(await ensureTypegpu(pm, pkg))) {
return;
}
await askForPkgs(pm, pkg);
}

export async function enhanceProject(cwd: string) {
Expand All @@ -35,7 +38,7 @@ export async function enhanceProject(cwd: string) {
}

// normal JSON.parse is fine because package.json cannot contain comments
const pkg = PackageJsonWithDepsSchema(JSON.parse(fs.readFileSync(pkgPath, 'utf-8')));
const pkg = PackageJsonSchema(JSON.parse(fs.readFileSync(pkgPath, 'utf-8')));
if (pkg instanceof type.errors) {
failAndExit('Could not parse package.json.', pkg.summary);
}
Expand Down
19 changes: 14 additions & 5 deletions packages/typegpu-cli/src/steps/typegpu.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
import type { Agent } from 'package-manager-detector';
import * as p from '@clack/prompts';

import { hasDependency } from '../utils/pkg.ts';
import type { PackageJsonWithDeps } from '../utils/types.ts';
import { appendVersion, hasDependency } from '../utils/pkg.ts';
import type { PackageJson } from '../utils/types.ts';
import { pmAdd } from '../utils/pm.ts';
import { confirmStep } from '../utils/prompts.ts';
import { multiselectPkgs } from '../utils/inputs.ts';

export async function ensureTypegpu(pm: Agent, pkg: PackageJsonWithDeps) {
export async function ensureTypegpu(pm: Agent, pkg: PackageJson): Promise<boolean> {
if (hasDependency(pkg, 'typegpu')) {
p.log.info('typegpu is already installed.');
return;
return true;
}
if (!(await confirmStep('Install typegpu?'))) return;
if (!(await confirmStep('Install typegpu?'))) return false;
pmAdd(pm, ['typegpu'], false);
// no p.log.success because pmAdd already logs it
return true;
}

export async function askForPkgs(pm: Agent, pkg: PackageJson) {
const pkgs = (await multiselectPkgs(pkg))?.map(({ pkg, ver }) => appendVersion(pkg, ver));
if (pkgs) {
Comment thread
aleksanderkatan marked this conversation as resolved.
pmAdd(pm, pkgs, false);
}
}
4 changes: 2 additions & 2 deletions packages/typegpu-cli/src/steps/vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { findConfig } from '../utils/config.ts';
import { hasDependency } from '../utils/pkg.ts';
import { pmAdd } from '../utils/pm.ts';
import { confirmStep, failAndExit } from '../utils/prompts.ts';
import type { PackageJsonWithDeps } from '../utils/types.ts';
import type { PackageJson } from '../utils/types.ts';

const VITE_CONFIG_NAMES = [
'vite.config.ts',
Expand Down Expand Up @@ -46,7 +46,7 @@ function createViteConfig(cwd: string) {
p.log.success('Created vite.config.ts.');
}

export async function ensureVite(cwd: string, pm: Agent, pkg: PackageJsonWithDeps) {
export async function askForVite(cwd: string, pm: Agent, pkg: PackageJson) {
if (hasDependency(pkg, 'unplugin-typegpu')) {
p.log.info('unplugin-typegpu is already installed.');
return;
Expand Down
4 changes: 2 additions & 2 deletions packages/typegpu-cli/src/steps/webgpu-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { hasDependency } from '../utils/pkg.ts';
import { findConfig } from '../utils/config.ts';
import { pmAdd } from '../utils/pm.ts';
import { confirmStep, failAndExit } from '../utils/prompts.ts';
import { TsConfigSchema, type PackageJsonWithDeps } from '../utils/types.ts';
import { TsConfigSchema, type PackageJson } from '../utils/types.ts';

const TS_CONFIG_NAMES = ['tsconfig.app.json', 'tsconfig.json'];

Expand Down Expand Up @@ -40,7 +40,7 @@ function addWebgpuTypesToTsconfig(filePath: string) {
fs.writeFileSync(filePath, stringify(tsconfig, null, 2) + '\n');
}

export async function ensureWebgpuTypes(cwd: string, pm: Agent, pkg: PackageJsonWithDeps) {
export async function askForWebgpuTypes(cwd: string, pm: Agent, pkg: PackageJson) {
if (hasDependency(pkg, '@webgpu/types')) {
p.log.info('@webgpu/types package is already installed.');
return;
Expand Down
19 changes: 16 additions & 3 deletions packages/typegpu-cli/src/utils/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import path from 'node:path';
import * as p from '@clack/prompts';

import { cancelExit, failAndExit } from './prompts.ts';
import { PackageJsonWithNameSchema } from './types.ts';
import { type } from 'arktype';
import { PackageJsonSchema } from './types.ts';
import { multiselectPkgs } from './inputs.ts';

const renameFiles = {
_gitignore: '.gitignore',
Expand Down Expand Up @@ -49,7 +50,11 @@ export async function prepareDirectory(cwd: string, projectDir: string) {
return dir;
}

export function copyTemplate(templateDir: string, projectDir: string, packageName: string) {
export async function scaffoldProject(
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dislike how this function became async, but we cannot ask for packages to add before reading the JSON (otherwise, we might suggest packages already present in the preset)

templateDir: string,
projectDir: string,
packageName: string,
) {
const entries = fs.readdirSync(templateDir);
for (const entry of entries.filter((f) => f !== '_package.json' && f !== 'index.html')) {
const src = path.join(templateDir, entry);
Expand All @@ -65,10 +70,18 @@ export function copyTemplate(templateDir: string, projectDir: string, packageNam

const srcPackage = path.join(templateDir, '_package.json');
const destPackage = path.join(projectDir, 'package.json');
const pkg = PackageJsonWithNameSchema(JSON.parse(fs.readFileSync(srcPackage, 'utf-8')));
const pkg = PackageJsonSchema(JSON.parse(fs.readFileSync(srcPackage, 'utf-8')));
if (pkg instanceof type.errors) {
failAndExit(`[INTERNAL] Invalid package.json in template ${templateDir}`, pkg.summary);
}
pkg.name = packageName;
const pkgs = await multiselectPkgs(pkg);
if (pkgs) {
pkg.dependencies ??= {};
for (const { pkg: dep, ver } of pkgs) {
pkg.dependencies[dep] = ver;
}
}

fs.writeFileSync(destPackage, JSON.stringify(pkg, null, 2) + '\n' /* to make oxfmt happy */);
}
22 changes: 22 additions & 0 deletions packages/typegpu-cli/src/utils/inputs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import * as p from '@clack/prompts';
import { cancelExit } from './prompts.ts';
import type { PackageJson } from './types.ts';
import { hasDependency, typegpuPkgs, VERSION } from './pkg.ts';

function isValidProjectDirectory(projectDir: string) {
return !/[<>:"\\|?*\s]|\/+$/.test(projectDir.trim());
Expand Down Expand Up @@ -43,3 +45,23 @@ export async function getPackageName(initialValue: string) {

return packageName.trim();
}

export async function multiselectPkgs(pkg: PackageJson) {
const options = typegpuPkgs.filter((entry) => !hasDependency(pkg, entry.value));
if (options.length === 0) {
p.log.info('All typegpu ecosystem packages are already installed.');
return;
}

const packages = await p.multiselect({
message: "Pick packages to add ('space' to select, 'enter' to confirm):",
options: options,
required: false,
});

if (p.isCancel(packages)) {
cancelExit();
}

return packages.map((pkgName) => ({ pkg: pkgName, ver: VERSION }));
}
25 changes: 23 additions & 2 deletions packages/typegpu-cli/src/utils/pkg.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
import type { PackageJsonWithDeps } from './types.ts';
import type { PackageJson } from './types.ts';

export function hasDependency(pkg: PackageJsonWithDeps, name: string) {
export function hasDependency(pkg: PackageJson, name: string) {
const deps = pkg.dependencies ?? {};
const devDeps = pkg.devDependencies ?? {};
const peerDeps = pkg.peerDependencies ?? {};
return name in deps || name in devDeps || name in peerDeps;
}

export const VERSION = '^0.11.0';
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded. If we wish, we can create a map from package to version in future.


export const typegpuPkgs = [
{ value: '@typegpu/color', hint: 'helpers for converting color spaces' },
// { value: '@typegpu/geometry', hint: 'helpers for drawing points and lines' },
// { value: '@typegpu/gl', hint: 'WebGL utilities for TypeGPU integration' },
{
value: '@typegpu/noise',
hint: 'helpers for Perlin noise and general purpose random number generation.',
},
{ value: '@typegpu/radiance-cascades', hint: 'implementation of Radiance Cascades algorithm' },
{ value: '@typegpu/react', hint: 'React hooks for TypeGPU integration' },
{ value: '@typegpu/sdf', hint: 'helpers for creating Signed Distance Fields' },
// { value: '@typegpu/sort', hint: 'implementations of scanning and sorting algorithms' },
{ value: '@typegpu/three', hint: 'Three.js utilities for TypeGPU integration' },
] as const satisfies { value: string; hint: string }[];

export function appendVersion(pkg: string, version: string) {
return `${pkg}@${version}`;
}
5 changes: 5 additions & 0 deletions packages/typegpu-cli/src/utils/pm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ function runCommand(command: string, args: string[], interactive?: boolean) {
}

export function pmAdd(pm: Agent, pkgs: string[], dev: boolean) {
if (pkgs.length === 0) {
p.log.success('No packages to install.');
return;
}

const args = dev ? ['-D', ...pkgs] : pkgs;
const cmd = resolveCommand(pm, 'add', args);
if (!cmd) {
Expand Down
10 changes: 3 additions & 7 deletions packages/typegpu-cli/src/utils/types.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import { type } from 'arktype';

export const PackageJsonWithDepsSchema = type({
export const PackageJsonSchema = type({
name: 'string',
'dependencies?': 'Record<string, string>',
'devDependencies?': 'Record<string, string>',
'peerDependencies?': 'Record<string, string>',
});
Comment on lines +3 to 8
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we care?

export type PackageJsonWithDeps = typeof PackageJsonWithDepsSchema.infer;

export const PackageJsonWithNameSchema = type({
name: 'string',
});
export type PackageJsonWithName = typeof PackageJsonWithNameSchema.infer;
export type PackageJson = typeof PackageJsonSchema.infer;

export const TsConfigSchema = type({
'compilerOptions?': {
Expand Down
Loading