Skip to content

Commit

Permalink
feat(nx-plugin): initial commit page generator/schematic (#577)
Browse files Browse the repository at this point in the history
  • Loading branch information
luishcastroc authored Jul 31, 2023
1 parent afb9188 commit 4a2de22
Show file tree
Hide file tree
Showing 7 changed files with 405 additions and 0 deletions.
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

0 comments on commit 4a2de22

Please sign in to comment.