Skip to content
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
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
},
"peerDependencies": {},
"devDependencies": {
"spectacle": "workspace:spectacle@*",
Copy link
Contributor

@scottrippey scottrippey Jul 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@carlos-kelly This is listed as a devDep, but it looks to me like it's being used as a runtime dependency ... does it somehow get "baked in"?
We could bring in spectacle as a prod dep just to know the latest version. Hopefully it'd get cached since post-CLI you'd install it again.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's a runtime it will be included in the entire bin when we build this cli as runnable. We likely need to do a script to extract out the version number from the other package.json rather than some kind of cross-package dependency. @gksander and I were pondering this, but I'm really mixed if typing the version is easier. We do that on one-page today.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I'm mixed too ... hard-coding it seems easy, but not as easy to maintain.

Copy link
Member

@ryan-roemer ryan-roemer Jul 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PNPM should work just with a "normal" spectacle dependency. My understanding is the workspace: is only needed when theres a very customize setup that disables PNPM workspace inference (?)

https://pnpm.io/workspaces#workspace-protocol-workspace

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we likely drop this, feels a bit over-engineered to avoid typing in a version number.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we do need to change this before publishing, because currently trying to run the CLI (outside this repo) I think will fail.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup! the CLI is not ready to be published yet. We still need to build the interactive prompts. Just flags are working.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we just want an "up-to-date" version number, tools like changesets, etc. can just take care of keeping this current on version/publish and then we could even have the CLI read it's own package.json:devDependencies.spectacle to infer the correct version...

"@types/node": "^18.0.3",
"nodemon": "^2.0.18",
"rimraf": "^3.0.2",
Expand Down
40 changes: 24 additions & 16 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ import { Command } from 'commander';
import cliSpinners from 'cli-spinners';
import logUpdate from 'log-update';
import {
writeBaseWebpackProjectFiles,
FileOptions,
writeWebpackProjectFiles,
writeOnePageHTMLFile
} from './templates';
} from './templates/file-writers';
import { version as SPECTACLE_VERSION } from 'spectacle/package.json';

type CLIOptions = {
type: 'tsx' | 'jsx' | 'mdx' | 'onepage';
name: string;
lang?: string;
port?: number;
};

let progressInterval: NodeJS.Timer;
Expand Down Expand Up @@ -41,11 +45,15 @@ const main = async () => {
'deck source type (choices: "tsx", "jsx", "mdx", "onepage")'
)
.requiredOption('-n, --name [name]', 'name of presentation')
.option(
'-l, --lang [lang]',
'language code for generated HTML document, default: en'
)
.option('-p, --port [port]', 'port for webpack dev server, default: 3000')
.parse(process.argv);

let i = 0;
const { type, name } = program.opts<CLIOptions>();
const snakeCaseName = name.toLowerCase().replace(/([^a-z0-9]+)/gi, '-');
const { type, name, lang = 'en', port = 3000 } = program.opts<CLIOptions>();

progressInterval = setInterval(() => {
const { frames } = cliSpinners.aesthetic;
Expand All @@ -57,24 +65,24 @@ const main = async () => {

await sleep(750);

const fileOptions: FileOptions = {
snakeCaseName: name.toLowerCase().replace(/([^a-z0-9]+)/gi, '-'),
name,
lang,
port,
enableTypeScriptSupport: type === 'tsx',
spectacleVersion: SPECTACLE_VERSION
};

switch (type) {
case 'jsx':
await writeBaseWebpackProjectFiles({
snakeCaseName,
name
});
await writeWebpackProjectFiles(fileOptions);
break;
case 'tsx':
await writeBaseWebpackProjectFiles({
snakeCaseName,
name
});
await writeWebpackProjectFiles(fileOptions);
break;
case 'onepage':
await writeOnePageHTMLFile({
snakeCaseName,
name
});
await writeOnePageHTMLFile(fileOptions);
break;
}

Expand Down
25 changes: 25 additions & 0 deletions packages/cli/src/templates/babel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
type BabelTemplateOptions = {
enableTypeScriptSupport: boolean;
};

export const babelTemplate = (options: BabelTemplateOptions) =>
`{
"presets": [
${options.enableTypeScriptSupport ? '"@babel/preset-typescript",' : ''}
["@babel/preset-env", { "modules": false }],
["@babel/preset-react", { "runtime": "automatic" }]
],
"plugins": [
"@babel/plugin-proposal-object-rest-spread",
"@babel/plugin-proposal-class-properties"
],
"env": {
"cjs": {
"presets": ["@babel/preset-env", "@babel/preset-react"]
},
"test": {
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
}
}
`;
72 changes: 72 additions & 0 deletions packages/cli/src/templates/file-writers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import path from 'path';
import { existsSync } from 'fs';
import { mkdir, writeFile, rm } from 'fs/promises';
import { htmlTemplate } from './html';
import { onePageTemplate } from './one-page';
import { webpackTemplate } from './webpack';
import { babelTemplate } from './babel';
import { packageTemplate } from './package';
import { indexTemplate } from './index';
import { tsconfigTemplate } from './tsconfig';

export type FileOptions = {
snakeCaseName: string;
name: string;
lang: string;
port: number;
enableTypeScriptSupport: boolean;
spectacleVersion: string;
};

export const writeWebpackProjectFiles = async ({
snakeCaseName,
name,
lang,
port,
enableTypeScriptSupport,
spectacleVersion
}: FileOptions) => {
const outPath = path.resolve(process.cwd(), snakeCaseName);

await rm(outPath, { recursive: true, force: true });

if (existsSync(outPath)) {
throw new Error(`Directory named ${snakeCaseName} already exists.`);
}
await mkdir(outPath, { recursive: true });
await writeFile(`${snakeCaseName}/index.html`, htmlTemplate({ name, lang }));
await writeFile(
`${snakeCaseName}/webpack.config.js`,
webpackTemplate({ port, usesTypeScript: enableTypeScriptSupport })
);
await writeFile(
`${snakeCaseName}/.babelrc`,
babelTemplate({ enableTypeScriptSupport })
);
await writeFile(
`${snakeCaseName}/package.json`,
packageTemplate({
usesTypeScript: enableTypeScriptSupport,
name: snakeCaseName,
spectacleVersion
})
);
await writeFile(
`${snakeCaseName}/index.${enableTypeScriptSupport ? 'tsx' : 'jsx'}`,
indexTemplate({
usesTypeScript: enableTypeScriptSupport,
name
})
);

enableTypeScriptSupport &&
(await writeFile(`${snakeCaseName}/tsconfig.json`, tsconfigTemplate()));
};

export const writeOnePageHTMLFile = async ({
snakeCaseName,
name,
lang
}: FileOptions) => {
await writeFile(`${snakeCaseName}.html`, onePageTemplate({ name, lang }));
};
3 changes: 2 additions & 1 deletion packages/cli/src/templates/html.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
type HTMLTemplateOptions = {
name: string;
lang: string;
};

export const htmlTemplate = (options: HTMLTemplateOptions) => `
<!DOCTYPE html>
<html>
<html lang="${options.lang}">
<head>
<title>${options.name}</title>
</head>
Expand Down
67 changes: 42 additions & 25 deletions packages/cli/src/templates/index.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,46 @@
import path from 'path';
import { existsSync } from 'fs';
import { mkdir, writeFile } from 'fs/promises';
import { htmlTemplate } from './html';
import { onePageTemplate } from './one-page';

type FileOptions = {
snakeCaseName: string;
type IndexTemplateOptions = {
name: string;
usesTypeScript: boolean;
};

export const writeBaseWebpackProjectFiles = async ({
snakeCaseName,
name
}: FileOptions) => {
const outPath = path.resolve(process.cwd(), snakeCaseName);
if (existsSync(outPath)) {
throw new Error(`Directory named ${snakeCaseName} already exists.`);
}
await mkdir(outPath, { recursive: true });
await writeFile(`${snakeCaseName}/index.html`, htmlTemplate({ name }));
};
export const indexTemplate = (options: IndexTemplateOptions) =>
`import React from 'react';
import { createRoot } from 'react-dom/client';
import { Slide, Deck, FlexBox, Heading, SpectacleLogo, Box, FullScreen, AnimatedProgress } from 'spectacle';
export const writeOnePageHTMLFile = async ({
snakeCaseName,
name
}: FileOptions) => {
await writeFile(`${snakeCaseName}.html`, onePageTemplate({ name }));
};
const template = () => (
<FlexBox
justifyContent="space-between"
position="absolute"
bottom={0}
width={1}
>
<Box padding="0 1em">
<FullScreen />
</Box>
<Box padding="1em">
<AnimatedProgress />
</Box>
</FlexBox>
);
const Presentation = () => (
<Deck template={template}>
<Slide>
<FlexBox height="100%">
<Heading>${options.name}</Heading>
</FlexBox>
</Slide>
<Slide>
<FlexBox height="100%">
<Heading fontSize="h2">Made with</Heading>
<SpectacleLogo size={300} />
</FlexBox>
</Slide>
</Deck>
);
createRoot(document.getElementById('app')${
options.usesTypeScript ? '!' : ''
}).render(<Presentation />);
`;
5 changes: 3 additions & 2 deletions packages/cli/src/templates/one-page.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
type OnePageTemplateOptions = {
name: string;
lang: string;
};

export const onePageTemplate = ({ name }: OnePageTemplateOptions) => `
export const onePageTemplate = ({ name, lang }: OnePageTemplateOptions) => `
<!DOCTYPE html>
<html lang="en">
<html lang="${lang}">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
Expand Down
45 changes: 45 additions & 0 deletions packages/cli/src/templates/package.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
type PackageTemplateOptions = {
name: string;
spectacleVersion: string;
usesTypeScript: boolean;
};

export const packageTemplate = (options: PackageTemplateOptions) =>
`{
"name": "${options.name}",
"private": true,
"scripts": {
"start": "webpack-dev-server --hot --config ./webpack.config.js",
"clean": "rimraf dist",
"build": "webpack --config ./webpack.config.js --mode production"
},
"dependencies": {
"spectacle": "^${options.spectacleVersion}",
"react": "^18.1.0",
"react-dom": "^18.1.0"
},
"devDependencies": {
"@babel/core": "^7.17.2",
"@babel/plugin-proposal-class-properties": "^7.12.1",
"@babel/plugin-proposal-object-rest-spread": "^7.12.1",
"@babel/preset-env": "^7.12.7",
"@babel/preset-react": "^7.16.7",
"babel-loader": "^8.0.6",
"html-webpack-plugin": "^5.3.1",
"style-loader": "^3.3.1",
"css-loader": "^5.1.3",
"file-loader": "^6.2.0",
"rimraf": "^3.0.0",
"webpack": "^5.68.0",
"webpack-cli": "^4.5.0",
"webpack-dev-server": "^4.7.4"${
options.usesTypeScript
? ',\n "typescript": "^4.5.2",' +
'\n "@babel/preset-typescript": "^7.16.0",' +
'\n "@types/react": "^18.0.12",' +
'\n "@types/react-dom": "^18.0.5"'
: ''
}
}
}
`;
20 changes: 20 additions & 0 deletions packages/cli/src/templates/tsconfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export const tsconfigTemplate = () =>
`{
"compilerOptions": {
"target": "ES6",
"lib": [
"DOM",
"ES2019"
],
"jsx": "react-jsx",
"module": "commonjs",
"moduleResolution": "node",
"allowUmdGlobalAccess": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
`;
32 changes: 32 additions & 0 deletions packages/cli/src/templates/webpack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
type WebpackTemplateOptions = {
port: number;
usesTypeScript: boolean;
};

export const webpackTemplate = (options: WebpackTemplateOptions) =>
`const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
mode: 'development',
context: __dirname,
entry: './index.${options.usesTypeScript ? 'tsx' : 'jsx'}',
output: {
path: path.join(__dirname, '/dist'),
filename: 'app.bundle.js'
},
devServer: {
port: ${options.port}
},
module: {
rules: [
{ test: /\\.[tj]sx?$/, use: ['babel-loader'] },
{ test: /\\.(png|svg|jpg|gif)$/, use: ['file-loader'] },
{ test: /\\.css$/, use: ['style-loader', 'css-loader'] }
]
},
plugins: [
new HtmlWebpackPlugin({ template: './index.html' }),
]
};
`;
Loading