Skip to content

Commit b522c9e

Browse files
authored
Merge pull request #24 from google/angular-ui
Add support for creating an Angular Material UI
2 parents bc155ae + 0dd1e72 commit b522c9e

File tree

10 files changed

+267
-17
lines changed

10 files changed

+267
-17
lines changed

.eslintignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
node_modules/*
1+
**/node_modules/*
22
build/*
3+
**/dist/*
34
testing

.eslintrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@
1515
"rules": {
1616
"prettier/prettier": "error"
1717
},
18-
"ignorePatterns": ["template/**/*"]
18+
"ignorePatterns": ["template/**/*", "template-ui/**/*"]
1919
}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ build/
44
.DS_Store
55
.clasp*.json
66
*.tgz
7+
.vscode/

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ After running the `init` command above, ASIDE will go ahead and do the following
7070

7171
ASIDE is using [clasp](https://github.com/google/clasp) to pull and push code from and to Apps Script
7272

73+
- **(Optionally) Create an Angular Material UI**
74+
75+
ASIDE will run the necessary commands to create an Angular application with Angular Material components, if the option is chosen
76+
7377
## Options
7478

7579
You can provide the `init` command with some convenience options:
@@ -124,6 +128,23 @@ dummy;
124128

125129
As long as anything from a file is being used, the entire file will be kept.
126130

131+
### The UI is not working on Apps Script
132+
133+
When installing Angular Material, if you chose `Include and enable animations`, you need to make some changes to `src/ui/src/app/app.config.ts`.
134+
135+
ASIDE currently doesn't support chunk files which will be generated for lazy-loading through `provideAnimationsAsync()`.
136+
137+
Change `app.config.ts` to:
138+
139+
```
140+
import { ApplicationConfig } from '@angular/core';
141+
import { provideAnimations } from '@angular/platform-browser/animations';
142+
143+
export const appConfig: ApplicationConfig = {
144+
providers: [provideAnimations()]
145+
};
146+
```
147+
127148
## Disclaimer
128149

129150
This is not an officially supported Google product.

deploy-ui.mjs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* Copyright 2023 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import fs from 'fs-extra';
18+
import path from 'path';
19+
20+
const cwd = process.cwd();
21+
const uiDist = path.join(cwd, 'src/ui/dist/ui/browser');
22+
const files = fs
23+
.readdirSync(uiDist)
24+
.filter(f => f.endsWith('.html') || f.endsWith('.js') || f.endsWith('.css'));
25+
26+
for (const filename of files) {
27+
let newName = filename;
28+
const oldPath = path.join(uiDist, filename);
29+
30+
if (path.extname(filename) === '.html') {
31+
newName = 'ui.html';
32+
const newPath = path.join(cwd, 'dist', newName);
33+
// Replace <script> tags with GAS <?!= ?> tags
34+
const scriptRegex = /<script src="([^"]*).js" type="module"><\/script>/g;
35+
const cssRegex = /<link rel="stylesheet" href="([^"]*).css".*(?=<\/head>)/g;
36+
let htmlContent = fs.readFileSync(oldPath).toString();
37+
htmlContent = htmlContent.replaceAll(
38+
scriptRegex,
39+
"<?!= include('$1'); ?>\n"
40+
);
41+
htmlContent = htmlContent.replaceAll(
42+
cssRegex,
43+
"\n<?!= include('$1'); ?>\n"
44+
);
45+
fs.writeFileSync(newPath, htmlContent);
46+
} else if (path.extname(filename) === '.js') {
47+
newName = path.format({ ...path.parse(filename), base: '', ext: '.html' });
48+
const newPath = path.join(cwd, 'dist', newName);
49+
// Add a <script> tag around the js code
50+
const jsContent = fs.readFileSync(oldPath).toString();
51+
fs.writeFileSync(
52+
newPath,
53+
`<script type="text/javascript">\n${jsContent}\n</script>`
54+
);
55+
} else {
56+
newName = path.format({ ...path.parse(filename), base: '', ext: '.html' });
57+
const newPath = path.join(cwd, 'dist', newName);
58+
const cssContent = fs.readFileSync(oldPath).toString();
59+
fs.writeFileSync(newPath, `<style>\n${cssContent}\n</style>`);
60+
}
61+
}

src/app.ts

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { fileURLToPath } from 'url';
2323
import writeFileAtomic from 'write-file-atomic';
2424

2525
import { ClaspHelper } from './clasp-helper.js';
26-
import { config } from './config.js';
26+
import { config, configForUi } from './config.js';
2727
import { PackageHelper } from './package-helper.js';
2828

2929
/**
@@ -36,10 +36,13 @@ export const app = null;
3636
const __filename = fileURLToPath(import.meta.url);
3737
const __dirname = path.dirname(__filename);
3838

39+
let CONFIG: typeof config;
40+
3941
export interface Options {
4042
yes: boolean;
4143
no: boolean;
4244
title: string;
45+
ui: boolean;
4346
}
4447

4548
/**
@@ -70,7 +73,7 @@ export async function handlePackageJson(options: Options) {
7073
// Synchronize scripts
7174
console.log(`${chalk.green('\u2714')}`, 'Adding scripts...');
7275
const existingScripts = packageJson.getScripts();
73-
for (const [name, script] of Object.entries(config.scripts)) {
76+
for (const [name, script] of Object.entries(CONFIG.scripts)) {
7477
if (name in existingScripts && existingScripts[name] !== script) {
7578
const replace = await query(
7679
`package.json already has a script for ${chalk.bold(name)}:\n` +
@@ -97,7 +100,7 @@ export async function handlePackageJson(options: Options) {
97100

98101
// Install dev dependencies
99102
console.log(`${chalk.green('\u2714')}`, 'Installing dependencies...');
100-
packageJson.installPackages(config.dependencies);
103+
packageJson.installPackages(CONFIG.dependencies);
101104
}
102105

103106
/**
@@ -190,11 +193,11 @@ async function readFile(path: string): Promise<string | undefined> {
190193
* @param {Options} options
191194
*/
192195
async function handleConfigMerge(options: Options) {
193-
for (const filename of Object.keys(config.filesMerge)) {
196+
for (const filename of Object.keys(CONFIG.filesMerge)) {
194197
const sourcePath = path.join(__dirname, '../../', filename);
195198
let sourceLines = (await readFile(sourcePath))?.split('\n');
196199

197-
const targetFile = await readFile(config.filesMerge[filename]);
200+
const targetFile = await readFile(CONFIG.filesMerge[filename]);
198201
const targetLines = targetFile?.split('\n') ?? [];
199202

200203
const missingLines =
@@ -205,7 +208,7 @@ async function handleConfigMerge(options: Options) {
205208
if (targetFile !== undefined) {
206209
const message =
207210
`${chalk.bold(
208-
config.filesMerge[filename]
211+
CONFIG.filesMerge[filename]
209212
)} already exists but is missing content\n` +
210213
missingLines.map(line => `+${chalk.green(line)}`).join('\n');
211214

@@ -217,7 +220,7 @@ async function handleConfigMerge(options: Options) {
217220
sourceLines = targetLines.concat(missingLines);
218221

219222
await writeFileAtomic(
220-
config.filesMerge[filename],
223+
CONFIG.filesMerge[filename],
221224
`${sourceLines.filter(item => item).join('\n')}\n`
222225
);
223226
}
@@ -229,25 +232,25 @@ async function handleConfigMerge(options: Options) {
229232
* @param {Options} options
230233
*/
231234
async function handleConfigCopy(options: Options) {
232-
for (const filename of Object.keys(config.filesCopy)) {
235+
for (const filename of Object.keys(CONFIG.filesCopy)) {
233236
try {
234237
const sourcePath = path.join(__dirname, '../../', filename);
235238
const source = await readFile(sourcePath);
236-
const target = await readFile(config.filesCopy[filename]);
239+
const target = await readFile(CONFIG.filesCopy[filename]);
237240

238241
if (source === target || typeof source === 'undefined') continue;
239242

240243
const writeFile = target
241244
? await query(
242-
`${chalk.bold(config.filesCopy[filename])} already exists`,
245+
`${chalk.bold(CONFIG.filesCopy[filename])} already exists`,
243246
'Overwrite',
244247
false,
245248
options
246249
)
247250
: true;
248251

249252
if (writeFile) {
250-
await writeFileAtomic(config.filesCopy[filename], source);
253+
await writeFileAtomic(CONFIG.filesCopy[filename], source);
251254
}
252255
} catch (e) {
253256
const err = e as Error & { code?: string };
@@ -260,10 +263,17 @@ async function handleConfigCopy(options: Options) {
260263

261264
/**
262265
* Handle putting template files in place.
266+
*
267+
* @param {Options} options
263268
*/
264-
async function handleTemplate() {
269+
async function handleTemplate(options: Options) {
265270
const cwd = process.cwd();
266-
const templates = path.join(__dirname, '../../template');
271+
let templates;
272+
if (options.ui) {
273+
templates = path.join(__dirname, '../../template-ui');
274+
} else {
275+
templates = path.join(__dirname, '../../template');
276+
}
267277

268278
const items = await fs.readdir(templates);
269279

@@ -319,7 +329,7 @@ async function handleClasp(options: Options) {
319329
// Prepare clasp project environment
320330
if (scriptIdDev) {
321331
console.log(`${chalk.green('\u2714')}`, `Cloning ${scriptIdDev}...`);
322-
claspHelper.cloneAndPull(scriptIdDev, scriptIdProd, 'dist');
332+
await claspHelper.cloneAndPull(scriptIdDev, scriptIdProd, 'dist');
323333
} else {
324334
console.log(`${chalk.green('\u2714')}`, `Creating ${options.title}...`);
325335
const res = await claspHelper.create(options.title, scriptIdProd, './dist');
@@ -353,8 +363,17 @@ export async function init(
353363
yes: flags.yes || false,
354364
no: flags.no || false,
355365
title: projectTitle,
366+
ui: flags.no || false,
356367
};
357368

369+
options.ui = await query('', 'Create an Angular UI?', false, options);
370+
371+
if (options.ui) {
372+
CONFIG = configForUi;
373+
} else {
374+
CONFIG = config;
375+
}
376+
358377
// Handle package.json
359378
await handlePackageJson(options);
360379

@@ -365,8 +384,16 @@ export async function init(
365384
await handleConfigMerge(options);
366385

367386
// Handle template
368-
await handleTemplate();
387+
await handleTemplate(options);
369388

370389
// Handle clasp
371390
await handleClasp(options);
391+
392+
if (options.ui) {
393+
console.log();
394+
console.log(
395+
'Make sure to run npm install to install all the Angular UI dependencies'
396+
);
397+
console.log();
398+
}
372399
}

src/config.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,73 @@ export const config: {
7676
'.prettierignore': '.prettierignore',
7777
},
7878
};
79+
80+
export const configForUi: {
81+
dependencies: string[];
82+
scripts: PackageJson.Scripts;
83+
filesCopy: Record<string, string>;
84+
filesMerge: Record<string, string>;
85+
} = {
86+
dependencies: [
87+
'@angular/cli',
88+
'@google/clasp',
89+
'@types/google-apps-script',
90+
'@types/jest',
91+
'@typescript-eslint/eslint-plugin',
92+
'eslint',
93+
'eslint-config-prettier',
94+
'eslint-plugin-prettier',
95+
'fs-extra',
96+
'gts',
97+
'inquirer@^8.0.0',
98+
'jest',
99+
'license-check-and-add',
100+
'ncp',
101+
'prettier',
102+
'rimraf',
103+
'rollup',
104+
'rollup-plugin-cleanup',
105+
'rollup-plugin-license',
106+
'rollup-plugin-typescript2',
107+
'ts-jest',
108+
'typescript',
109+
],
110+
scripts: {
111+
'preinstall':
112+
'(cd src/ && ng new --skip-git --skip-tests=true --routing=false --ssr=false --style=css --standalone ui && cd ui/ && ng add --skip-confirmation @angular/material)',
113+
'clean': 'rimraf build dist',
114+
'lint':
115+
'npm run license && eslint --fix --no-error-on-unmatched-pattern src/ test/',
116+
'bundle': 'rollup --no-treeshake -c rollup.config.mjs',
117+
'build': 'npm run clean && npm run bundle',
118+
'build-ui': 'npm run build --prefix src/ui',
119+
'license': 'license-check-and-add add -f license-config.json',
120+
'test': 'jest test/ --passWithNoTests --detectOpenHandles',
121+
'test-ui': 'npm run test --prefix src/ui',
122+
'deploy':
123+
'npm run lint && npm run test && npm run build && ncp appsscript.json dist/appsscript.json && ncp .clasp-dev.json .clasp.json && npm run build-ui && npm run deploy-ui && clasp push -f',
124+
'deploy-ui': 'node deploy-ui.mjs',
125+
'deploy:prod':
126+
'npm run lint && npm run test && npm run build && ncp appsscript.json dist/appsscript.json && ncp .clasp-prod.json .clasp.json && npm run build-ui && npm run deploy-ui && clasp push',
127+
'serve-ui': '(cd src/ui && ng serve)',
128+
'postinstall': '(cd src/ui && npm install)',
129+
},
130+
filesCopy: {
131+
'.editorconfig': '.editorconfig',
132+
'.eslintrc.json': '.eslintrc.json',
133+
'.prettierrc.json': '.prettierrc.json',
134+
'jest.config.json': 'jest.config.json',
135+
'LICENSE': 'LICENSE',
136+
'license-config.json': 'license-config.json',
137+
'license-header.txt': 'license-header.txt',
138+
'rollup.config.mjs': 'rollup.config.mjs',
139+
'deploy-ui.mjs': 'deploy-ui.mjs',
140+
'tsconfig.json': 'tsconfig.json',
141+
},
142+
filesMerge: {
143+
'dist/.gitignore-target': '.gitignore',
144+
'.claspignore': '.claspignore',
145+
'.eslintignore': '.eslintignore',
146+
'.prettierignore': '.prettierignore',
147+
},
148+
};

template-ui/src/example-module.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Copyright 2023 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
export function hello() {
17+
return 'Hello Apps Script!';
18+
}

0 commit comments

Comments
 (0)