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
5 changes: 4 additions & 1 deletion .agents/skills/migrate-to-rstack-cli/references/rslint.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ Read this reference when the project uses `@rslint/core`, `rslint.config.*`, `rs
```ts
import { define } from 'rstack';

define.lint(({ js, ts }) => [js.configs.recommended, ts.configs.recommendedTypeChecked]);
define.lint(({ js, ts }) => [
js.configs.recommended,
ts.configs.recommendedTypeChecked,
]);
```

Preserve existing presets and rules during migration.
Expand Down
4 changes: 3 additions & 1 deletion examples/test-inline-projects/tests/dom.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ import App from '../src/App';
test('renders the app in a DOM environment', () => {
render(<App />);

expect(screen.getByRole('heading', { name: 'Rstack React SSR' })).toBeTruthy();
expect(
screen.getByRole('heading', { name: 'Rstack React SSR' }),
).toBeTruthy();
});
49 changes: 40 additions & 9 deletions packages/create-rstack/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import {
create,
select,
} from '@rstackjs/create-toolkit';
import { access, appendFile, mkdir, readFile, writeFile } from 'node:fs/promises';
import {
access,
appendFile,
mkdir,
readFile,
writeFile,
} from 'node:fs/promises';
import path from 'node:path';

const packageRoot = path.join(import.meta.dirname, '..');
Expand Down Expand Up @@ -87,12 +93,15 @@ const getTemplateName = async ({ template }: Argv): Promise<string> => {
}),
);

return resolveTemplateName(documentationType === 'basic' ? 'doc' : 'doc-i18n');
return resolveTemplateName(
documentationType === 'basic' ? 'doc' : 'doc-i18n',
);
}

const templateType = checkCancel<string>(
await select({
message: projectType === 'app' ? 'Select framework' : 'Select library type',
message:
projectType === 'app' ? 'Select framework' : 'Select library type',
options:
projectType === 'app'
? [
Expand Down Expand Up @@ -129,12 +138,32 @@ const getTemplateName = async ({ template }: Argv): Promise<string> => {
};

const getStagedConfig = (templateName: string): string => {
const scriptExtensions = ['js', 'jsx', 'ts', 'tsx', 'mjs', 'cjs', 'mts', 'cts'];
const formatExtensions = ['json', 'jsonc', 'md', 'mdx', 'css', 'html', 'yml', 'yaml'];
const scriptExtensions = [
'js',
'jsx',
'ts',
'tsx',
'mjs',
'cjs',
'mts',
'cts',
];
const formatExtensions = [
'json',
'jsonc',
'md',
'mdx',
'css',
'html',
'yml',
'yaml',
];
const componentExtensions = ['svelte', 'vue'];
const templateFormatExtensions = [
...formatExtensions,
...componentExtensions.filter((extension) => templateName.includes(extension)),
...componentExtensions.filter((extension) =>
templateName.includes(extension),
),
];

return [
Expand All @@ -157,7 +186,9 @@ const injectStagedSetup = async ({
return;
}

const configExtension = await access(path.join(distFolder, 'rstack.config.ts')).then(
const configExtension = await access(
path.join(distFolder, 'rstack.config.ts'),
).then(
() => 'ts',
() => 'js',
);
Expand All @@ -167,8 +198,8 @@ const injectStagedSetup = async ({
};

packageJson.scripts = Object.fromEntries(
Object.entries({ ...packageJson.scripts, prepare: 'rs setup' }).sort(([left], [right]) =>
left.localeCompare(right),
Object.entries({ ...packageJson.scripts, prepare: 'rs setup' }).sort(
([left], [right]) => left.localeCompare(right),
),
);

Expand Down
127 changes: 100 additions & 27 deletions packages/create-rstack/tests/create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,29 +31,65 @@ type SourceTemplate = {

const sourceTemplates: SourceTemplate[] = [
{ template: 'app-vanilla', sourceExtension: 'js', testFile: 'dom.test.js' },
{ template: 'app-vanilla-ts', sourceExtension: 'ts', testFile: 'dom.test.ts' },
{
template: 'app-vanilla-ts',
sourceExtension: 'ts',
testFile: 'dom.test.ts',
},
{ template: 'app-react', sourceExtension: 'jsx', testFile: 'index.test.jsx' },
{ template: 'app-react-ts', sourceExtension: 'tsx', testFile: 'index.test.tsx' },
{ template: 'app-preact', sourceExtension: 'jsx', testFile: 'index.test.jsx' },
{ template: 'app-preact-ts', sourceExtension: 'tsx', testFile: 'index.test.tsx' },
{
template: 'app-react-ts',
sourceExtension: 'tsx',
testFile: 'index.test.tsx',
},
{
template: 'app-preact',
sourceExtension: 'jsx',
testFile: 'index.test.jsx',
},
{
template: 'app-preact-ts',
sourceExtension: 'tsx',
testFile: 'index.test.tsx',
},
{ template: 'app-vue', sourceExtension: 'js', testFile: 'index.test.js' },
{ template: 'app-vue-ts', sourceExtension: 'ts', testFile: 'index.test.ts' },
{ template: 'app-lit', sourceExtension: 'js', testFile: 'index.test.js' },
{ template: 'app-lit-ts', sourceExtension: 'ts', testFile: 'index.test.ts' },
{ template: 'app-svelte', sourceExtension: 'js', testFile: 'index.test.js' },
{ template: 'app-svelte-ts', sourceExtension: 'ts', testFile: 'index.test.ts' },
{
template: 'app-svelte-ts',
sourceExtension: 'ts',
testFile: 'index.test.ts',
},
{ template: 'app-solid', sourceExtension: 'jsx', testFile: 'index.test.jsx' },
{ template: 'app-solid-ts', sourceExtension: 'tsx', testFile: 'index.test.tsx' },
{
template: 'app-solid-ts',
sourceExtension: 'tsx',
testFile: 'index.test.tsx',
},
{ template: 'lib-node', sourceExtension: 'js', testFile: 'index.test.js' },
{ template: 'lib-node-ts', sourceExtension: 'ts', testFile: 'index.test.ts' },
{ template: 'lib-react', sourceExtension: 'jsx', testFile: 'index.test.jsx' },
{ template: 'lib-react-ts', sourceExtension: 'tsx', testFile: 'index.test.tsx' },
{
template: 'lib-react-ts',
sourceExtension: 'tsx',
testFile: 'index.test.tsx',
},
{ template: 'lib-vue', sourceExtension: 'js', testFile: 'index.test.js' },
{ template: 'lib-vue-ts', sourceExtension: 'ts', testFile: 'index.test.ts' },
{ template: 'lib-svelte', sourceExtension: 'js', testFile: 'index.test.js' },
{ template: 'lib-svelte-ts', sourceExtension: 'ts', testFile: 'index.test.ts' },
{
template: 'lib-svelte-ts',
sourceExtension: 'ts',
testFile: 'index.test.ts',
},
{ template: 'lib-solid', sourceExtension: 'jsx', testFile: 'index.test.jsx' },
{ template: 'lib-solid-ts', sourceExtension: 'tsx', testFile: 'index.test.tsx' },
{
template: 'lib-solid-ts',
sourceExtension: 'tsx',
testFile: 'index.test.tsx',
},
];

const docTemplates = [
Expand All @@ -74,14 +110,25 @@ const docTemplates = [
];

const getCheckScript = (template: string, hasTypeScript: boolean): string =>
hasTypeScript && !templatesWithoutTypeCheck.has(template) ? typeCheckScript : checkScript;
hasTypeScript && !templatesWithoutTypeCheck.has(template)
? typeCheckScript
: checkScript;

const readProjectPackage = async (projectDirectory: string): Promise<ProjectPackage> =>
JSON.parse(await readFile(path.join(projectDirectory, 'package.json'), 'utf8')) as ProjectPackage;
const readProjectPackage = async (
projectDirectory: string,
): Promise<ProjectPackage> =>
JSON.parse(
await readFile(path.join(projectDirectory, 'package.json'), 'utf8'),
) as ProjectPackage;

const expectFiles = async (projectDirectory: string, files: string[]): Promise<void> => {
const expectFiles = async (
projectDirectory: string,
files: string[],
): Promise<void> => {
for (const file of files) {
await expect(access(path.join(projectDirectory, file))).resolves.toBeUndefined();
await expect(
access(path.join(projectDirectory, file)),
).resolves.toBeUndefined();
}
};

Expand All @@ -92,10 +139,16 @@ const expectStagedSetup = async (
): Promise<void> => {
expect(scripts.prepare).toBe('rs setup');
expect(
await readFile(path.join(projectDirectory, '.rstack', 'hooks', 'pre-commit'), 'utf8'),
await readFile(
path.join(projectDirectory, '.rstack', 'hooks', 'pre-commit'),
'utf8',
),
).toBe('rs staged\n');
expect(
await readFile(path.join(projectDirectory, `rstack.config.${configExtension}`), 'utf8'),
await readFile(
path.join(projectDirectory, `rstack.config.${configExtension}`),
'utf8',
),
).toContain('define.staged({');
};

Expand All @@ -109,7 +162,10 @@ const expectNoStagedSetup = async (
access(path.join(projectDirectory, '.rstack', 'hooks', 'pre-commit')),
).rejects.toThrow();
expect(
await readFile(path.join(projectDirectory, `rstack.config.${configExtension}`), 'utf8'),
await readFile(
path.join(projectDirectory, `rstack.config.${configExtension}`),
'utf8',
),
).not.toContain('define.staged({');
};

Expand All @@ -122,8 +178,14 @@ const expectProjectSetup = async (
const packageJson = await readProjectPackage(projectDirectory);

expect(packageJson.name).toBe('my-app');
expect(packageJson.scripts.check).toBe(getCheckScript(template, hasTypeScript));
await expectStagedSetup(projectDirectory, configExtension, packageJson.scripts);
expect(packageJson.scripts.check).toBe(
getCheckScript(template, hasTypeScript),
);
await expectStagedSetup(
projectDirectory,
configExtension,
packageJson.scripts,
);

const tsconfig = access(path.join(projectDirectory, 'tsconfig.json'));
if (hasTypeScript) {
Expand All @@ -135,7 +197,9 @@ const expectProjectSetup = async (

afterEach(async () => {
await Promise.all(
tempDirectories.splice(0).map((directory) => rm(directory, { recursive: true, force: true })),
tempDirectories
.splice(0)
.map((directory) => rm(directory, { recursive: true, force: true })),
);
});

Expand All @@ -154,7 +218,8 @@ const createProject = async (
tempDirectories.push(tempDirectory);

if (initializeGitIn) {
const gitDirectory = initializeGitIn === 'project' ? projectDirectory : tempDirectory;
const gitDirectory =
initializeGitIn === 'project' ? projectDirectory : tempDirectory;
await mkdir(gitDirectory, { recursive: true });
await execFileAsync('git', ['init', '--quiet'], { cwd: gitDirectory });
}
Expand Down Expand Up @@ -209,14 +274,22 @@ test.each(sourceTemplates)(
files.push('src/env.d.ts');
}

await expectProjectSetup(projectDirectory, template, configExtension, hasTypeScript);
await expectProjectSetup(
projectDirectory,
template,
configExtension,
hasTypeScript,
);
await expectFiles(projectDirectory, files);
},
);

test.each(docTemplates)('creates the $template template', async ({ template, files }) => {
const projectDirectory = await createProject(template);
test.each(docTemplates)(
'creates the $template template',
async ({ template, files }) => {
const projectDirectory = await createProject(template);

await expectProjectSetup(projectDirectory, template, 'ts', true);
await expectFiles(projectDirectory, files);
});
await expectProjectSetup(projectDirectory, template, 'ts', true);
await expectFiles(projectDirectory, files);
},
);
3 changes: 2 additions & 1 deletion packages/rstack/rslib.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { defineConfig } from '@rslib/core';
import prettierPkgJson from 'prettier/package.json' with { type: 'json' };
import pkgJson from './package.json' with { type: 'json' };

const fullyMinifiedChunks = /(?:fmt(?:Lsp|Plugins)?|sortPackageJsonPlugin|staged)\.js$/;
const fullyMinifiedChunks =
/(?:fmt(?:Lsp|Plugins)?|sortPackageJsonPlugin|staged)\.js$/;

export default defineConfig({
dts: true,
Expand Down
Loading