Skip to content

Commit

Permalink
Added the 'pa solution init' command solving pnp#953
Browse files Browse the repository at this point in the history
  • Loading branch information
YannickRe authored and waldekmastykarz committed Sep 15, 2019
1 parent 8dba1ad commit 477ab91
Show file tree
Hide file tree
Showing 19 changed files with 774 additions and 39 deletions.
38 changes: 38 additions & 0 deletions docs/manual/docs/cmd/pa/solution/solution-init.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# pa solution init

Initializes a directory with a new CDS solution project

## Usage

```sh
pa solution init [options]
```

## Options

Option|Description
------|-----------
`--help`|output usage information
`--publisherName <publisherName>`|Name of the CDS solution publisher.
`--publisherPrefix <publisherPrefix>`|Customization prefix value for the CDS solution publisher.
`-o, --output [output]`|Output type. `json|text`. Default `text`
`--verbose`|Runs command with verbose logging
`--debug`|Runs command with debug logging

## Remarks

PublisherName only allows characters within the ranges `[A-Z]`, `[a-z]`, `[0-9]`, or `_`. The first character may only be in the ranges `[A-Z]`, `[a-z]`, or `_`.

PublisherPrefix must be 2 to 8 characters long, can only consist of alpha-numerics, must start with a letter, and cannot start with 'mscrm'.

## Examples

Initializes a CDS solution project using _yourPublisherName_ as publisher name and _ypn_ as publisher prefix

```sh
pa solution init --publisherName yourPublisherName --publisherPrefix ypn
```

## More information

- Create and build a custom component: [https://docs.microsoft.com/en-us/powerapps/developer/component-framework/create-custom-controls-using-pcf](https://docs.microsoft.com/en-us/powerapps/developer/component-framework/create-custom-controls-using-pcf)
7 changes: 7 additions & 0 deletions docs/manual/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ nav:
- PowerApps (pa):
- pcf:
- pcf init: 'cmd/pa/pcf/pcf-init.md'
- solution:
- solution init: 'cmd/pa/solution/solution-init.md'
- Microsoft Planner (planner):
- task:
- task list: 'cmd/planner/task/task-list.md'
Expand Down Expand Up @@ -355,6 +357,11 @@ nav:
- Office 365 (tenant):
- id:
- id get: 'cmd/tenant-id-get.md'
- PowerApps (pa):
- pcf:
- pcf init: 'cmd/pa/pcf/pcf-init.md'
- solution:
- solution init: 'cmd/pa/solution/solution-init.md'
- Concepts:
- 'Persisting connection': 'concepts/persisting-connection.md'
- 'Authorization and access tokens': 'concepts/authorization-tokens.md'
Expand Down
45 changes: 28 additions & 17 deletions scripts/copy-files.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
const fs = require('fs');
const path = require('path');
const assetsDir = 'dist/o365/spfx/commands/project/project-upgrade/assets';
if (!fs.existsSync(assetsDir)) {
fs.mkdirSync(assetsDir);
}
fs.copyFileSync('src/o365/spfx/commands/project/project-upgrade/assets/tab20x20.png', path.join(assetsDir, 'tab20x20.png'));
fs.copyFileSync('src/o365/spfx/commands/project/project-upgrade/assets/tab96x96.png', path.join(assetsDir, 'tab96x96.png'));

const paPcfAssetsSourceDir = 'src/o365/pa/commands/pcf/pcf-init/assets';
const paPcfAssetsDir = 'dist/o365/pa/commands/pcf/pcf-init/assets';
if (!fs.existsSync(paPcfAssetsDir)) {
fs.mkdirSync(paPcfAssetsDir);
const mkdirNotExistsSync = (path) => {
if (!fs.existsSync(path)) {
fs.mkdirSync(path);
}
}

const getFilePaths = (folderPath) => {
Expand All @@ -21,14 +15,31 @@ const getFilePaths = (folderPath) => {
return [...filePaths, ...dirFiles];
};

getFilePaths(paPcfAssetsSourceDir).forEach(file => {
const copyFile = (file, sourceDir, destinationDir) => {
const fileName = path.basename(file);
const filePath = path.relative(paPcfAssetsSourceDir, path.dirname(file));
const destinationFilePath = path.join(paPcfAssetsDir, filePath);
const filePath = path.relative(sourceDir, path.dirname(file));
const destinationFilePath = path.join(destinationDir, filePath);

if (!fs.existsSync(destinationFilePath)) {
fs.mkdirSync(destinationFilePath);
}
mkdirNotExistsSync(destinationFilePath);

fs.copyFileSync(file, path.join(destinationFilePath, fileName));
});
};

const assetsDir = 'dist/o365/spfx/commands/project/project-upgrade/assets';
mkdirNotExistsSync(assetsDir);
fs.copyFileSync('src/o365/spfx/commands/project/project-upgrade/assets/tab20x20.png', path.join(assetsDir, 'tab20x20.png'));
fs.copyFileSync('src/o365/spfx/commands/project/project-upgrade/assets/tab96x96.png', path.join(assetsDir, 'tab96x96.png'));

const paPcfInitAssetsSourceDir = 'src/o365/pa/commands/pcf/pcf-init/assets';
const paPcfInitCmdDir = 'dist/o365/pa/commands/pcf/pcf-init';
const paPcfInitAssetsDir = 'dist/o365/pa/commands/pcf/pcf-init/assets';
mkdirNotExistsSync(paPcfInitCmdDir);
mkdirNotExistsSync(paPcfInitAssetsDir);
getFilePaths(paPcfInitAssetsSourceDir).forEach(file => copyFile(file, paPcfInitAssetsSourceDir, paPcfInitAssetsDir));

const paSolutionInitAssetsSourceDir = 'src/o365/pa/commands/solution/solution-init/assets';
const paSolutionInitCmdDir = 'dist/o365/pa/commands/solution/solution-init';
const paSolutionInitAssetsDir = 'dist/o365/pa/commands/solution/solution-init/assets';
mkdirNotExistsSync(paSolutionInitCmdDir);
mkdirNotExistsSync(paSolutionInitAssetsDir);
getFilePaths(paSolutionInitAssetsSourceDir).forEach(file => copyFile(file, paSolutionInitAssetsSourceDir, paSolutionInitAssetsDir));
8 changes: 6 additions & 2 deletions src/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ export default class Utils {
return `${tenantUrl}${serverRelativeUrl}`;
}

public static isJavascriptReservedWord(input: string) {
public static isJavascriptReservedWord(input: string): boolean {
const javascriptReservedWords: string[] = [
"arguments",
"await",
Expand Down Expand Up @@ -502,6 +502,10 @@ export default class Utils {
"onmousedown",
"onsubmit"
];
return input && !input.split('.').every(value => !~javascriptReservedWords.indexOf(value));
return !!input && !input.split('.').every(value => !~javascriptReservedWords.indexOf(value));
}

public static isValidFileName(input: string): boolean {
return !!input && !/^((\..*)|COM\d|CLOCK\$|LPT\d|AUX|NUL|CON|PRN|(.*[\u{d800}-\u{dfff}]+.*))$/iu.test(input) && !/^(.*\.\..*)$/i.test(input);
}
}
3 changes: 2 additions & 1 deletion src/o365/pa/commands.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const prefix: string = 'pa';

export default {
PCF_INIT: `${prefix} pcf init`
PCF_INIT: `${prefix} pcf init`,
SOLUTION_INIT: `${prefix} solution init`
};
2 changes: 1 addition & 1 deletion src/o365/pa/commands/pcf/pcf-init.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as assert from 'assert';
import * as fs from 'fs';
import * as path from 'path';
import Utils from '../../../../Utils';
import TemplateInstantiator from './pcf-init/template-instantiator';
import TemplateInstantiator from '../../template-instantiator';

describe(commands.PCF_INIT, () => {
let vorpal: Vorpal;
Expand Down
18 changes: 9 additions & 9 deletions src/o365/pa/commands/pcf/pcf-init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import Command, {
CommandError
} from '../../../../Command';
import Utils from '../../../../Utils';
import TemplateInstantiator from "./pcf-init/template-instantiator";
import { TemplateVariables } from "./pcf-init/template-variables";
import TemplateInstantiator from "../../template-instantiator";
import { PcfInitVariables } from "./pcf-init/pcf-init-variables";

const vorpal: Vorpal = require('../../../../vorpal-init');

Expand Down Expand Up @@ -61,7 +61,7 @@ class PaPcfInitCommand extends Command {
const workingDirectory: string = process.cwd();
const workingDirectoryName: string = path.basename(workingDirectory);
const componentDirectory: string = path.join(workingDirectory, args.options.name);
const variables: TemplateVariables = {
const variables: PcfInitVariables = {
"$namespaceplaceholder$": args.options.namespace,
"$controlnameplaceholder$": args.options.name,
"$pcfProjectName$": workingDirectoryName,
Expand All @@ -72,7 +72,7 @@ class PaPcfInitCommand extends Command {
if (this.verbose) {
cmd.log(`name: ${args.options.name}`);
cmd.log(`namespace: ${args.options.namespace}`);
cmd.log(`template: ${args.options.name}`);
cmd.log(`template: ${args.options.template}`);
cmd.log(`pcfTemplatePath: ${pcfTemplatePath}`);
cmd.log(`pcfComponentTemplatePath: ${pcfComponentTemplatePath}`);
cmd.log(`workingDirectory: ${workingDirectory}`);
Expand Down Expand Up @@ -120,15 +120,15 @@ class PaPcfInitCommand extends Command {

public validate(): CommandValidate {
return (args: CommandArgs): boolean | string => {

if (fs.readdirSync(process.cwd()).some(fn => fn.endsWith('proj'))) {
return 'The current directory cannot be used because it already contains a project. Please create a new directory and retry the operation.';
return 'PowerApps component framework project creation failed. The current directory already contains a project. Please create a new directory and retry the operation.';
}

const workingDirectoryName: string = path.basename(process.cwd());
if (!workingDirectoryName
|| /^((\..*)|COM\d|CLOCK\$|LPT\d|AUX|NUL|CON|PRN|(.*[\u{d800}-\u{dfff}]+.*))$/iu.test(workingDirectoryName)
|| /^(.*\.\..*)$/i.test(workingDirectoryName)) {
return `Empty or invalid project name '${workingDirectoryName}'`;
if (!Utils.isValidFileName(workingDirectoryName))
{
return `Empty or invalid project name '${workingDirectoryName}'`;
}

if (args.options.name) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export interface TemplateVariables {
export interface PcfInitVariables {
[key:string]: string;
"$namespaceplaceholder$": string;
"$controlnameplaceholder$": string;
Expand Down
Loading

0 comments on commit 477ab91

Please sign in to comment.