diff --git a/generators/app/generate-html.js b/generators/app/generate-html.js
index 2e19200..85cefb9 100644
--- a/generators/app/generate-html.js
+++ b/generators/app/generate-html.js
@@ -12,7 +12,7 @@ export default {
id: 'tool-html',
aliases: ['html', 'typescript', 'ts'],
name: 'HTML with TypeScript',
-
+
/**
* @param {Generator} generator
* @param {ToolConfig} toolConfig
@@ -24,63 +24,46 @@ export default {
await prompts.askForGit(generator, toolConfig);
await prompts.askForPackageManager(generator, toolConfig);
},
-
+
/**
* @param {Generator} generator
* @param {ToolConfig} toolConfig
*/
writing: (generator, toolConfig) => {
// Copy package.json template
- generator.fs.copyTpl(
- generator.templatePath('html/package.json'),
- generator.destinationPath('package.json'),
- toolConfig
- );
+ generator.fs.copyTpl(generator.templatePath('html/package.json'), generator.destinationPath('package.json'), toolConfig);
// Copy tsconfig.json
- generator.fs.copy(
- generator.templatePath('html/tsconfig.json'),
- generator.destinationPath('tsconfig.json')
- );
+ generator.fs.copy(generator.templatePath('html/tsconfig.json'), generator.destinationPath('tsconfig.json'));
+
+ // Copy tsconfig.node.json
+ generator.fs.copy(generator.templatePath('html/tsconfig.node.json'), generator.destinationPath('tsconfig.node.json'));
+
+ // Copy tsconfig.node.json
+ generator.fs.copy(generator.templatePath('html/tsconfig.node.json'), generator.destinationPath('tsconfig.node.json'));
+
+ // Copy vite.config.ts
+ generator.fs.copy(generator.templatePath('html/vite.config.ts'), generator.destinationPath('vite.config.ts'));
// Copy .gitignore if git is initialized
if (toolConfig.gitInit) {
- generator.fs.copy(
- generator.templatePath('html/gitignore'),
- generator.destinationPath('.gitignore')
- );
+ generator.fs.copy(generator.templatePath('html/gitignore'), generator.destinationPath('.gitignore'));
}
// Copy .npmignore
- generator.fs.copy(
- generator.templatePath('html/npmignore'),
- generator.destinationPath('.npmignore')
- );
+ generator.fs.copy(generator.templatePath('html/npmignore'), generator.destinationPath('.npmignore'));
// Copy README
- generator.fs.copyTpl(
- generator.templatePath('html/README.md'),
- generator.destinationPath('README.md'),
- toolConfig
- );
+ generator.fs.copyTpl(generator.templatePath('html/README.md'), generator.destinationPath('README.md'), toolConfig);
// Copy source files
- generator.fs.copy(
- generator.templatePath('html/src/index.html'),
- generator.destinationPath('src/index.html')
- );
+ generator.fs.copy(generator.templatePath('html/src/index.html'), generator.destinationPath('src/index.html'));
- generator.fs.copy(
- generator.templatePath('html/src/app.ts'),
- generator.destinationPath('src/app.ts')
- );
+ generator.fs.copy(generator.templatePath('html/src/app.ts'), generator.destinationPath('src/app.ts'));
- generator.fs.copy(
- generator.templatePath('html/src/styles.css'),
- generator.destinationPath('src/styles.css')
- );
+ generator.fs.copy(generator.templatePath('html/src/styles.css'), generator.destinationPath('src/styles.css'));
},
-
+
/**
* @param {Generator} generator
* @param {ToolConfig} toolConfig
@@ -88,5 +71,5 @@ export default {
endMessage: (generator, toolConfig) => {
generator.log('Your HTML/TypeScript tool is ready!');
generator.log('Build your tool with: ' + (toolConfig.pkgManager === 'npm' ? 'npm run build' : toolConfig.pkgManager + ' build'));
- }
+ },
};
diff --git a/generators/app/templates/html/README.md b/generators/app/templates/html/README.md
index 5ce616d..02cb1cb 100644
--- a/generators/app/templates/html/README.md
+++ b/generators/app/templates/html/README.md
@@ -19,13 +19,10 @@
│ ├── index.ts # Tool logic (TypeScript)
│ └── styles.css # Styling
├── dist/ # Compiled output (after build)
-│ ├── index.html
-│ ├── index.js
-│ ├── index.js.map
-│ └── styles.css
├── package.json
+├── README.md
├── tsconfig.json
-└── README.md
+└── vite.config.ts
```
## Installation
@@ -38,18 +35,44 @@ npm install
## Development
-Build the tool:
+**Build the tool:**
```bash
npm run build
```
-Watch mode for development:
+**Dev build with sourcemaps (watch mode):**
```bash
-npm run watch
+npm run dev-watch
```
+**Validate tool package:**
+
+```bash
+npm run validate
+```
+
+**Shrinkwrap package:**
+
+```bash
+npm run finalize-package
+```
+
+or;
+
+```bash
+npm shrinkwrap
+```
+
+**Publish new version:**
+
+```bash
+npm run publish-package
+```
+
+_Further tool development documentation is available @ https://docs.powerplatformtoolbox.com/tool-development_
+
## Usage in ToolBox
1. Build the tool using `npm run build`
@@ -72,9 +95,9 @@ console.log(context.accessToken);
```typescript
await window.toolboxAPI.showNotification({
- title: 'Success',
- body: 'Operation completed',
- type: 'success'
+ title: 'Success',
+ body: 'Operation completed',
+ type: 'success',
});
```
@@ -82,8 +105,8 @@ await window.toolboxAPI.showNotification({
```typescript
window.toolboxAPI.onToolboxEvent((event, payload) => {
- console.log('Event:', payload.event);
- console.log('Data:', payload.data);
+ console.log('Event:', payload.event);
+ console.log('Data:', payload.data);
});
```
diff --git a/generators/app/templates/html/package.json b/generators/app/templates/html/package.json
index f29e251..8826bec 100644
--- a/generators/app/templates/html/package.json
+++ b/generators/app/templates/html/package.json
@@ -1,28 +1,32 @@
{
- "name": "<%= name %>",
- "version": "0.1.0",
- "displayName": "<%= displayName %>",
- "description": "<%= description %>",
- "main": "index.html",
- "author": "Power Platform ToolBox",
- "license": "MIT",
- "engines": {
- "node": ">=18.0.0"
- },
- "scripts": {
- "build": "tsc && npm run copy-html && npm run copy-css",
- "copy-html": "shx cp src/index.html dist/",
- "copy-css": "shx cp src/styles.css dist/",
- "watch": "tsc --watch",
- "finalize-package": "npm shrinkwrap"
- },
- "devDependencies": {
- "@pptb/types": "^1.0.1",
- "typescript": "^5.0.0",
- "shx": "^0.4.0"
- },
- "files": [
- "dist",
- "npm-shrinkwrap.json"
- ]
+ "name": "<%= name %>",
+ "version": "0.1.0",
+ "displayName": "<%= displayName %>",
+ "description": "<%= description %>",
+ "main": "index.html",
+ "author": "Power Platform ToolBox",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "scripts": {
+ "build": "tsc && vite build",
+ "dev": "vite",
+ "watch": "vite build --watch",
+ "dev-watch": "vite build --mode development --watch",
+ "preview": "vite preview",
+ "finalize-package": "npm shrinkwrap",
+ "validate": "pptb-validate",
+ "version-stable": "npm version patch --no-git-tag-version",
+ "publish-package": "npm run build && npm run version-stable && npm publish --access public"
+ },
+ "devDependencies": {
+ "@pptb/types": "^1.2.0",
+ "typescript": "^5.0.0",
+ "vite": "^8.0.3"
+ },
+ "files": [
+ "dist",
+ "npm-shrinkwrap.json"
+ ]
}
diff --git a/generators/app/templates/html/src/app.ts b/generators/app/templates/html/src/app.ts
index fb5f612..82bfacf 100644
--- a/generators/app/templates/html/src/app.ts
+++ b/generators/app/templates/html/src/app.ts
@@ -2,7 +2,7 @@
/**
* HTML Sample Tool for Power Platform Tool Box
- *
+ *
* This sample demonstrates:
* - ToolBox API usage (connections, utils, terminal, events)
* - Dataverse API usage (CRUD, queries, metadata)
@@ -127,17 +127,13 @@ function subscribeToEvents() {
*/
function setupEventHandlers() {
// Notification buttons
- document.getElementById('show-success-btn')?.addEventListener('click', () =>
- showNotification('Success!', 'Operation completed successfully', 'success'));
-
- document.getElementById('show-info-btn')?.addEventListener('click', () =>
- showNotification('Information', 'This is an informational message', 'info'));
-
- document.getElementById('show-warning-btn')?.addEventListener('click', () =>
- showNotification('Warning', 'Please review this warning', 'warning'));
-
- document.getElementById('show-error-btn')?.addEventListener('click', () =>
- showNotification('Error', 'An error has occurred', 'error'));
+ document.getElementById('show-success-btn')?.addEventListener('click', () => showNotification('Success!', 'Operation completed successfully', 'success'));
+
+ document.getElementById('show-info-btn')?.addEventListener('click', () => showNotification('Information', 'This is an informational message', 'info'));
+
+ document.getElementById('show-warning-btn')?.addEventListener('click', () => showNotification('Warning', 'Please review this warning', 'warning'));
+
+ document.getElementById('show-error-btn')?.addEventListener('click', () => showNotification('Error', 'An error has occurred', 'error'));
// Utility buttons
document.getElementById('copy-clipboard-btn')?.addEventListener('click', copyToClipboard);
@@ -173,7 +169,7 @@ async function showNotification(title: string, body: string, type: 'success' | '
title,
body,
type,
- duration: 3000
+ duration: 3000,
});
log(`Notification shown: ${title} - ${body}`, type);
} catch (error) {
@@ -189,7 +185,7 @@ async function copyToClipboard() {
const data = {
timestamp: new Date().toISOString(),
connection: currentConnection?.name || 'No connection',
- message: 'This data was copied from the HTML Sample Tool'
+ message: 'This data was copied from the HTML Sample Tool',
};
await toolbox.utils.copyToClipboard(JSON.stringify(data, null, 2));
@@ -219,18 +215,17 @@ async function saveDataToFile() {
try {
const data = {
timestamp: new Date().toISOString(),
- connection: currentConnection ? {
- name: currentConnection.name,
- url: currentConnection.url,
- environment: currentConnection.environment
- } : null,
- message: 'Export from HTML Sample Tool'
+ connection: currentConnection
+ ? {
+ name: currentConnection.name,
+ url: currentConnection.url,
+ environment: currentConnection.environment,
+ }
+ : null,
+ message: 'Export from HTML Sample Tool',
};
- const filePath = await toolbox.utils.saveFile(
- 'sample-export.json',
- JSON.stringify(data, null, 2)
- );
+ const filePath = await toolbox.fileSystem.saveFile('sample-export.json', JSON.stringify(data, null, 2));
if (filePath) {
await showNotification('File Saved', `File saved to: ${filePath}`, 'success');
@@ -249,15 +244,15 @@ async function saveDataToFile() {
async function createTerminal() {
try {
currentTerminal = await toolbox.terminal.create({
- name: 'HTML Sample Terminal'
+ name: 'HTML Sample Terminal',
});
log(`Terminal created: ${currentTerminal.name} (${currentTerminal.id})`, 'success');
-
+
// Enable command buttons
const executeBtn = document.getElementById('execute-command-btn') as HTMLButtonElement;
const closeBtn = document.getElementById('close-terminal-btn') as HTMLButtonElement;
-
+
if (executeBtn) executeBtn.disabled = false;
if (closeBtn) closeBtn.disabled = false;
@@ -306,7 +301,7 @@ async function closeTerminal() {
// Disable command buttons
const executeBtn = document.getElementById('execute-command-btn') as HTMLButtonElement;
const closeBtn = document.getElementById('close-terminal-btn') as HTMLButtonElement;
-
+
if (executeBtn) executeBtn.disabled = true;
if (closeBtn) closeBtn.disabled = true;
@@ -401,7 +396,7 @@ async function createContact() {
try {
const firstnameInput = document.getElementById('contact-firstname') as HTMLInputElement;
const lastnameInput = document.getElementById('contact-lastname') as HTMLInputElement;
-
+
const output = document.getElementById('crud-output');
if (output) output.textContent = 'Creating contact...\n';
@@ -409,7 +404,7 @@ async function createContact() {
firstname: firstnameInput.value,
lastname: lastnameInput.value,
telephone1: '555-0100',
- description: 'Created by HTML Sample Tool'
+ description: 'Created by HTML Sample Tool',
});
createdId = result.id;
@@ -450,7 +445,7 @@ async function updateContact() {
await dataverse.update('contact', createdId, {
description: 'Updated by HTML Sample Tool at ' + new Date().toISOString(),
- telephone1: '555-0200'
+ telephone1: '555-0200',
});
if (output) {
@@ -556,20 +551,20 @@ function log(message: string, type: 'info' | 'success' | 'warning' | 'error' = '
const timestamp = new Date().toLocaleTimeString();
const logEntry = document.createElement('div');
logEntry.className = `log-entry ${type}`;
-
+
const timestampSpan = document.createElement('span');
timestampSpan.className = 'log-timestamp';
timestampSpan.textContent = `[${timestamp}]`;
-
+
const messageSpan = document.createElement('span');
messageSpan.textContent = message;
-
+
logEntry.appendChild(timestampSpan);
logEntry.appendChild(document.createTextNode(' '));
logEntry.appendChild(messageSpan);
logDiv.insertBefore(logEntry, logDiv.firstChild);
-
+
// Keep only last 50 entries
while (logDiv.children.length > 50) {
logDiv.removeChild(logDiv.lastChild!);
diff --git a/generators/app/templates/html/tsconfig.json b/generators/app/templates/html/tsconfig.json
index 914a0ee..bee0b71 100644
--- a/generators/app/templates/html/tsconfig.json
+++ b/generators/app/templates/html/tsconfig.json
@@ -1,25 +1,25 @@
{
"compilerOptions": {
"target": "ES2022",
- "module": "ES2022",
+ "useDefineForClassFields": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"],
- "outDir": "./dist",
- "rootDir": "./src",
- "strict": true,
- "esModuleInterop": true,
+ "module": "ESNext",
"skipLibCheck": true,
- "forceConsistentCasingInFileNames": true,
+ "types": ["@pptb/types"],
+
+ /* Bundler mode */
"moduleResolution": "bundler",
- "declaration": false,
- "sourceMap": true,
- "allowSyntheticDefaultImports": true,
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
"isolatedModules": true,
- "useDefineForClassFields": true,
- "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
- "include": ["src/**/*"],
- "exclude": ["node_modules", "dist"]
+ "include": ["src"],
+ "references": [{ "path": "./tsconfig.node.json" }]
}
diff --git a/generators/app/templates/html/tsconfig.node.json b/generators/app/templates/html/tsconfig.node.json
new file mode 100644
index 0000000..6675f46
--- /dev/null
+++ b/generators/app/templates/html/tsconfig.node.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true,
+ "strict": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/generators/app/templates/html/vite.config.ts b/generators/app/templates/html/vite.config.ts
new file mode 100644
index 0000000..0af4139
--- /dev/null
+++ b/generators/app/templates/html/vite.config.ts
@@ -0,0 +1,25 @@
+import { defineConfig } from "vite";
+
+export default defineConfig((configEnv) => {
+ return {
+ base: "./src",
+ root: "./src",
+ build: {
+ outDir: "../dist",
+ assetsDir: "assets",
+ cssCodeSplit: false,
+ sourcemap: configEnv.mode === "development",
+ rollupOptions: {
+ output: {
+ // Use IIFE format for compatibility with iframe srcdoc loading
+ // ES modules can have issues when loaded via file:// URLs in iframes
+ format: "iife",
+ // Bundle everything into a single file to avoid module loading issues
+ inlineDynamicImports: true,
+ // Disable chunking since we're bundling everything
+ manualChunks: undefined,
+ },
+ },
+ },
+ };
+});
diff --git a/generators/app/templates/react/README.md b/generators/app/templates/react/README.md
index 373e9c0..0e889cc 100644
--- a/generators/app/templates/react/README.md
+++ b/generators/app/templates/react/README.md
@@ -36,24 +36,44 @@ npm install
## Development
-Start development server with HMR:
+**Build the tool:**
```bash
-npm run dev
+npm run build
```
-Build the tool:
+**Dev build with sourcemaps (watch mode):**
```bash
-npm run build
+npm run dev-watch
+```
+
+**Validate tool package:**
+
+```bash
+npm run validate
```
-Preview production build:
+**Shrinkwrap package:**
```bash
-npm run preview
+npm run finalize-package
```
+or;
+
+```bash
+npm shrinkwrap
+```
+
+**Publish new version:**
+
+```bash
+npm run publish-package
+```
+
+_Further tool development documentation is available @ https://docs.powerplatformtoolbox.com/tool-development_
+
## Usage in ToolBox
1. Build the tool using `npm run build`
@@ -76,9 +96,9 @@ console.log(context.accessToken);
```typescript
await window.toolboxAPI.showNotification({
- title: 'Success',
- body: 'Operation completed',
- type: 'success'
+ title: 'Success',
+ body: 'Operation completed',
+ type: 'success',
});
```
@@ -86,8 +106,8 @@ await window.toolboxAPI.showNotification({
```typescript
window.toolboxAPI.onToolboxEvent((event, payload) => {
- console.log('Event:', payload.event);
- console.log('Data:', payload.data);
+ console.log('Event:', payload.event);
+ console.log('Data:', payload.data);
});
```
diff --git a/generators/app/templates/react/package.json b/generators/app/templates/react/package.json
index 774ab17..e19b6a7 100644
--- a/generators/app/templates/react/package.json
+++ b/generators/app/templates/react/package.json
@@ -13,8 +13,12 @@
"build": "tsc && vite build",
"dev": "vite",
"watch": "vite build --watch",
+ "dev-watch": "vite build --mode development --watch",
"preview": "vite preview",
- "finalize-package": "npm shrinkwrap"
+ "finalize-package": "npm shrinkwrap",
+ "validate": "pptb-validate",
+ "version-stable": "npm version patch --no-git-tag-version",
+ "publish-package": "npm run build && npm run version-stable && npm publish --access public"
},
"dependencies": {
"react": "^18.3.1",
diff --git a/generators/app/templates/react/vite.config.ts b/generators/app/templates/react/vite.config.ts
index caebc51..10b881d 100644
--- a/generators/app/templates/react/vite.config.ts
+++ b/generators/app/templates/react/vite.config.ts
@@ -18,46 +18,49 @@ function fixHtmlForPPTB(): Plugin {
html = html.replace(/\s*crossorigin/g, '');
// Clean up extra spaces around attributes
html = html.replace(/\s+>/g, '>');
-
+
// Move script tags from head to end of body
// IIFE executes immediately, so DOM must be ready
const scriptRegex = /(