Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(nx-plugin): initial commit page generator/schematic #577

Merged
merged 7 commits into from
Jul 31, 2023
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
12 changes: 12 additions & 0 deletions packages/nx-plugin/generators.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,25 @@
"description": "Analog preset for create-nx-workspace",
"x-use-standalone-layout": true,
"hidden": true
},
"page": {
"factory": "./src/generators/page/generator",
"schema": "./src/generators/page/schema.json",
"description": "Creates a new Analog page in the given or default project.",
"aliases": ["p"]
}
},
"schematics": {
"app": {
"factory": "./src/generators/app/compat",
"schema": "./src/generators/app/schema.json",
"description": "Generates an Analog application"
},
"page": {
"factory": "./src/generators/page/generator#analogPageGeneratorSchematic",
"schema": "./src/generators/page/schema.json",
"description": "Creates a new Analog page in the given or default project.",
"aliases": ["p"]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`analog-page generator > should create analog page correctly > page 1`] = `
"import { Component } from '@angular/core';

@Component({
standalone: true,
imports: [],
template: \` <p>home page works!!</p> \`,
})
export default class HomePage {}
"
`;

exports[`analog-page generator > should create analog page with metadata correctly > page 1`] = `
"import { RouteMeta } from '@analogjs/router';
import { Component } from '@angular/core';

export const routeMeta: RouteMeta = {
title: 'Home Page',
};

@Component({
standalone: true,
imports: [],
template: \` <p>home page works!!</p> \`,
})
export default class HomePage {}
"
`;

exports[`analog-page generator > should create analog page with redirect correctly > page 1`] = `
"import { RouteMeta } from '@analogjs/router';

export const routeMeta: RouteMeta = {
redirectTo: '/home',
pathMatch: 'full',
};
"
`;

exports[`analog-page generator > should create analog page with subfolder correctly > page 1`] = `
"import { Component } from '@angular/core';

@Component({
standalone: true,
imports: [],
template: \` <p>post page works!!</p> \`,
})
export default class PostPage {}
"
`;

exports[`analog-page generator > should create analog page with subfolder correctly > page 2`] = `
"import { Component } from '@angular/core';

@Component({
standalone: true,
imports: [],
template: \` <p>products page works!!</p> \`,
})
export default class ProductsPage {}
"
`;

exports[`analog-page generator > should create analog page with subfolder correctly > page 3`] = `
"import { Component } from '@angular/core';

@Component({
standalone: true,
imports: [],
template: \` <p>productsProductId page works!!</p> \`,
})
export default class ProductsProductIdPage {}
"
`;

exports[`analog-page generator > should create analog page with subfolder correctly > page 4`] = `
"import { Component } from '@angular/core';

@Component({
standalone: true,
imports: [],
template: \` <p>blog page works!!</p> \`,
})
export default class BlogPage {}
"
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<% if(redirectPage || metadata ) { %>import { RouteMeta } from '@analogjs/router'; <% } %>
<% if(!redirectPage) { %> import { Component } from '@angular/core'; <% } %>

<% if(!redirectPage) { %>
<% if(metadata) { %>
export const routeMeta: RouteMeta = {
title: '<%= title %>',
};
<% } %>
@Component({
standalone: true,
imports: [],
template: `
<p><%= propertyName %> page works!!</p>
`,
})
export default class <%= className %>Page {
} <% } else { %>
export const routeMeta: RouteMeta = {
redirectTo: '<%= redirectPath %>',
pathMatch: '<%= pathMatch %>',
};<% } %>
129 changes: 129 additions & 0 deletions packages/nx-plugin/src/generators/page/generator.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import {
Tree,
addProjectConfiguration,
names,
readProjectConfiguration,
} from '@nx/devkit';

import { analogPageGenerator } from './generator';
import { AnalogPageGeneratorSchema } from './schema';

describe('analog-page generator', () => {
const setup = async (options: AnalogPageGeneratorSchema) => {
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
addProjectConfiguration(tree, options.pathname, {
projectType: 'application',
sourceRoot: `apps/${names(options.project).fileName}/src`,
root: `apps/${names(options.project).fileName}`,
});
const config = readProjectConfiguration(tree, options.pathname);
return {
tree,
config,
};
};
let tree: Tree;

beforeEach(() => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
});

it('should create analog page correctly', async () => {
const options: AnalogPageGeneratorSchema = {
pathname: 'home',
project: 'test',
redirectPage: false,
metadata: false,
};

await setup(options);
await analogPageGenerator(tree, options);
expect(
tree.read('apps/test/src/app/pages/home.page.ts', 'utf-8')
).toMatchSnapshot('page');
});

it('should generate an error if the page is a redirect and the path is not provided', async () => {
const options: AnalogPageGeneratorSchema = {
pathname: 'home',
project: 'test',
redirectPage: true,
metadata: false,
};

await setup(options);
await expect(analogPageGenerator(tree, options)).rejects.toThrow(
'A redirectPath is required when redirectPage is true.'
);
});

it('should create analog page with metadata correctly', async () => {
const options: AnalogPageGeneratorSchema = {
pathname: 'home',
project: 'test',
redirectPage: false,
metadata: true,
title: 'Home Page',
};

await setup(options);
await analogPageGenerator(tree, options);
expect(
tree.read('apps/test/src/app/pages/home.page.ts', 'utf-8')
).toMatchSnapshot('page');
});

it('should create analog page with redirect correctly', async () => {
const options: AnalogPageGeneratorSchema = {
pathname: 'home',
project: 'test',
redirectPage: true,
metadata: false,
redirectPath: '/home',
pathMatch: 'full',
};

await setup(options);
await analogPageGenerator(tree, options);
expect(
tree.read('apps/test/src/app/pages/home.page.ts', 'utf-8')
).toMatchSnapshot('page');
});

it('should create analog page with subfolder correctly', async () => {
const options: AnalogPageGeneratorSchema = {
pathname: 'blog/post',
project: 'test',
redirectPage: false,
metadata: false,
};

await setup(options);
await analogPageGenerator(tree, options);
expect(
tree.read('apps/test/src/app/pages/blog/post.page.ts', 'utf-8')
).toMatchSnapshot('page');

options.pathname = 'products/[products]';
await analogPageGenerator(tree, options);
expect(
tree.read('apps/test/src/app/pages/products/[products].page.ts', 'utf-8')
).toMatchSnapshot('page');

options.pathname = 'products/products.[productId]';
await analogPageGenerator(tree, options);
expect(
tree.read(
'apps/test/src/app/pages/products/products.[productId].page.ts',
'utf-8'
)
).toMatchSnapshot('page');

options.pathname = '(blog)';
await analogPageGenerator(tree, options);
expect(
tree.read('apps/test/src/app/pages/(blog).page.ts', 'utf-8')
).toMatchSnapshot('page');
});
});
81 changes: 81 additions & 0 deletions packages/nx-plugin/src/generators/page/generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import {
convertNxGenerator,
formatFiles,
generateFiles,
getWorkspaceLayout,
names,
offsetFromRoot,
stripIndents,
Tree,
} from '@nx/devkit';
import * as path from 'path';
import { AnalogPageGeneratorSchema, NormalizedSchema } from './schema';

function normalizeOptions(
tree: Tree,
options: AnalogPageGeneratorSchema
): NormalizedSchema {
const projectRoot = `${getWorkspaceLayout(tree).appsDir}/${options.project}`;
return {
...options,
projectRoot,
};
}

function generateFileName(input: string) {
const pattern = /^[a-zA-Z0-9]+\.\[[a-zA-Z0-9-]+\]$/;
if (pattern.test(input)) {
return input.replace(/\[[a-zA-Z0-9-]+\]/, (match) => {
const wordId = match.slice(1, -1);
const camelCaseWordId = wordId.replace(/-([a-zA-Z0-9])/g, (_, letter) =>
letter.toUpperCase()
);
return `[${camelCaseWordId}]`;
});
} else {
return input;
}
}

function addFiles(tree: Tree, options: NormalizedSchema) {
const splitName = options.pathname.split('/');
const routeName = splitName[splitName.length - 1];
const fileName = generateFileName(routeName);
const templateOptions = {
...options,
...names(routeName),
name: names(routeName).fileName,
offsetFromRoot: offsetFromRoot(options.projectRoot),
template: '',
fileName,
};

const pageFolders = options.pathname.toLowerCase().split('/');

const pageDir = path.join(
options.projectRoot,
`/src/app/pages/${pageFolders.slice(0, -1)}`
);

generateFiles(tree, path.join(__dirname, 'files'), pageDir, templateOptions);
}

export async function analogPageGenerator(
tree: Tree,
options: AnalogPageGeneratorSchema
) {
const normalizedOptions = normalizeOptions(tree, options);
if (options.redirectPage && !options.redirectPath) {
throw new Error(
stripIndents`A redirectPath is required when redirectPage is true.`
);
}
addFiles(tree, normalizedOptions);

await formatFiles(tree);
}

export const analogPageGeneratorSchematic =
convertNxGenerator(analogPageGenerator);

export default analogPageGenerator;
13 changes: 13 additions & 0 deletions packages/nx-plugin/src/generators/page/schema.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export interface AnalogPageGeneratorSchema {
pathname: string;
project: string;
metadata?: boolean;
title?: string;
redirectPage?: boolean;
redirectPath?: string;
pathMatch?: string;
}

export interface NormalizedSchema extends AnalogPageGeneratorSchema {
projectRoot: string;
}
Loading