@temporalio/create 0.23.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,79 @@
1
+ /* eslint-disable no-empty */
2
+ // Modified from: https://github.com/vercel/next.js/blob/2425f4703c4c6164cecfdb6aa8f80046213f0cc6/packages/create-next-app/helpers/git.ts
3
+ import { execSync } from 'child_process';
4
+ import { rm } from 'fs/promises';
5
+ import path from 'path';
6
+ import prompts from 'prompts';
7
+
8
+ const NOT_A_GIT_REPOSITORY_STATUS_CODE = 128;
9
+
10
+ function isInGitRepository(): boolean {
11
+ try {
12
+ execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
13
+ return true;
14
+ } catch (error: any) {
15
+ if (error.status === NOT_A_GIT_REPOSITORY_STATUS_CODE) {
16
+ return false;
17
+ } else {
18
+ // Unknown error. To be safe, assume we're in a repo.
19
+ return true;
20
+ }
21
+ }
22
+ }
23
+
24
+ const HG_ERROR_STATUS_CODE = 255;
25
+
26
+ function isInMercurialRepository(): boolean {
27
+ try {
28
+ execSync('hg --cwd . root', { stdio: 'ignore' });
29
+ return true;
30
+ } catch (error: any) {
31
+ // There isn't anything more specific about `error` that we can pattern match against
32
+ if (error.status === HG_ERROR_STATUS_CODE && /Command failed/.test(error.message)) {
33
+ return false;
34
+ } else {
35
+ // Unknown error. To be safe, assume we're in a repo.
36
+ return true;
37
+ }
38
+ }
39
+ }
40
+
41
+ export async function tryGitInit(root: string, useGit?: boolean): Promise<boolean> {
42
+ if (useGit === false) {
43
+ return false;
44
+ }
45
+ let didInit = false;
46
+ const exec = (command: string) => execSync(command, { stdio: 'ignore', cwd: root });
47
+
48
+ try {
49
+ // If user didn't include --use-git, and they might be in an existing repo,
50
+ // ask before `git init`ing
51
+ if (useGit === undefined && (isInGitRepository() || isInMercurialRepository())) {
52
+ const res = await prompts({
53
+ type: 'confirm',
54
+ name: 'shouldInit',
55
+ message: `Would you like me to initialize a git repository for the project?`,
56
+ });
57
+
58
+ if (!res.shouldInit) {
59
+ return false;
60
+ }
61
+ }
62
+
63
+ exec('git init');
64
+ didInit = true;
65
+
66
+ exec('git checkout -b main');
67
+
68
+ exec('git add -A');
69
+ exec('git commit -m "Initial commit from @temporalio/create"');
70
+ return true;
71
+ } catch (e) {
72
+ if (didInit) {
73
+ try {
74
+ await rm(path.join(root, '.git'), { recursive: true, force: true });
75
+ } catch (_) {}
76
+ }
77
+ return false;
78
+ }
79
+ }
@@ -0,0 +1,46 @@
1
+ import test from 'ava';
2
+ import dedent from 'dedent';
3
+ import { readFile, rm, writeFile } from 'fs/promises';
4
+ import os from 'os';
5
+ import path from 'path';
6
+ import { v4 as uuid } from 'uuid';
7
+ import { replaceSdkVersion } from './install.js';
8
+ import { makeDir } from './make-dir.js';
9
+
10
+ test('replaceSdkVersion according to configured level', async (t) => {
11
+ console.log('debug windows');
12
+ // console.log('import.meta.url', import.meta.url);
13
+ // console.log('URL', new URL('../../test', import.meta.url));
14
+ const tempDir = path.join(os.tmpdir(), uuid());
15
+ console.log('tempDir:', tempDir);
16
+ await makeDir(tempDir);
17
+
18
+ // const { pathname: tempDir } = new URL('../../test', import.meta.url);
19
+ const packageJson = path.join(tempDir, 'package.json');
20
+ console.log('packageJson', packageJson);
21
+ await writeFile(
22
+ packageJson,
23
+ dedent`
24
+ {
25
+ "name": "test-create-project",
26
+ "version": "0.1.0",
27
+ "private": true,
28
+ "dependencies": {
29
+ "@temporalio/activity": "^1.0.0",
30
+ "@temporalio/client": "^1.0.0",
31
+ "@temporalio/worker": "^1.0.0",
32
+ "@temporalio/workflow": "^1.0.0",
33
+ "nanoid": "3.x"
34
+ }
35
+ }
36
+ `
37
+ );
38
+
39
+ await replaceSdkVersion({ root: tempDir, sdkVersion: 'foo' });
40
+ const replaced = JSON.parse(await readFile(packageJson, 'utf8'));
41
+ t.is(replaced.dependencies['@temporalio/activity'], 'foo');
42
+ t.is(replaced.dependencies['@temporalio/client'], 'foo');
43
+ t.is(replaced.dependencies.nanoid, '3.x');
44
+
45
+ await rm(tempDir, { recursive: true });
46
+ });
@@ -0,0 +1,66 @@
1
+ // Modified from: https://github.com/vercel/next.js/blob/2425f4703c4c6164cecfdb6aa8f80046213f0cc6/packages/create-next-app/helpers/install.ts
2
+ import { readFile, writeFile } from 'fs/promises';
3
+
4
+ import { spawn } from './subprocess.js';
5
+ import { isUrlOk } from './samples.js';
6
+
7
+ interface InstallArgs {
8
+ root: string;
9
+
10
+ /**
11
+ * Indicate whether to install packages using Yarn.
12
+ */
13
+ useYarn?: boolean;
14
+ sdkVersion?: string;
15
+ }
16
+
17
+ /**
18
+ * Spawn a package manager installation with either Yarn or NPM.
19
+ *
20
+ * @returns A Promise that resolves once the installation is finished.
21
+ */
22
+ export function install({ root, useYarn }: InstallArgs): Promise<void> {
23
+ const npm = /^win/.test(process.platform) ? 'npm.cmd' : 'npm';
24
+ const command: string = useYarn ? 'yarn' : npm;
25
+
26
+ return spawn(command, ['install'], {
27
+ cwd: root,
28
+ stdio: 'inherit',
29
+ env: { ...process.env, ADBLOCK: '1', DISABLE_OPENCOLLECTIVE: '1' },
30
+ });
31
+ }
32
+
33
+ export async function updateNodeVersion({ root }: InstallArgs): Promise<void> {
34
+ const currentNodeVersion = +process.versions.node.split('.')[0];
35
+ const versionAlreadyInPackageJson = 16;
36
+ const minimumValidVersion = 14;
37
+
38
+ if (currentNodeVersion >= minimumValidVersion && currentNodeVersion !== versionAlreadyInPackageJson) {
39
+ const packageName = `@tsconfig/node${currentNodeVersion}`;
40
+ const fileName = `${root}/package.json`;
41
+
42
+ const packageExists = await isUrlOk(`https://registry.npmjs.org/${packageName}`);
43
+ if (packageExists) {
44
+ let fileString = (await readFile(fileName)).toString();
45
+ await writeFile(fileName, fileString.replace(`@tsconfig/node${versionAlreadyInPackageJson}`, packageName));
46
+
47
+ const tsconfigJson = `${root}/tsconfig.json`;
48
+ fileString = (await readFile(tsconfigJson)).toString();
49
+ await writeFile(tsconfigJson, fileString.replace(`@tsconfig/node${versionAlreadyInPackageJson}`, packageName));
50
+ }
51
+
52
+ await writeFile(`${root}/.nvmrc`, currentNodeVersion.toString());
53
+ }
54
+ }
55
+
56
+ export async function replaceSdkVersion({ root, sdkVersion }: InstallArgs): Promise<void> {
57
+ const fileName = `${root}/package.json`;
58
+
59
+ const packageJson = JSON.parse(await readFile(fileName, 'utf8'));
60
+ for (const packageName in packageJson.dependencies) {
61
+ if (packageName.startsWith('@temporalio/')) {
62
+ packageJson.dependencies[packageName] = sdkVersion;
63
+ }
64
+ }
65
+ await writeFile(fileName, JSON.stringify(packageJson));
66
+ }
@@ -0,0 +1,44 @@
1
+ // Modified from: https://github.com/vercel/next.js/blob/2425f4703c4c6164cecfdb6aa8f80046213f0cc6/packages/create-next-app/helpers/is-online.ts
2
+ import { execSync } from 'child_process';
3
+ import dns from 'dns';
4
+ import { URL } from 'url';
5
+
6
+ // Look for any proxy the user might have configured on their machine
7
+ function getProxy(): string | undefined {
8
+ if (process.env.https_proxy) {
9
+ return process.env.https_proxy;
10
+ }
11
+
12
+ try {
13
+ const httpsProxy = execSync('npm config get https-proxy').toString().trim();
14
+ return httpsProxy !== 'null' ? httpsProxy : undefined;
15
+ } catch (e) {
16
+ return;
17
+ }
18
+ }
19
+
20
+ export function testIfThisComputerIsOnline(): Promise<boolean> {
21
+ return new Promise((resolve) => {
22
+ dns.lookup('github.com', (registryErr) => {
23
+ if (!registryErr) {
24
+ return resolve(true);
25
+ }
26
+
27
+ // If we can't reach the registry directly, see if the user has a proxy
28
+ // configured. If they do, see if the proxy is reachable.
29
+ const proxy = getProxy();
30
+ if (!proxy) {
31
+ return resolve(false);
32
+ }
33
+
34
+ const { hostname } = new URL(proxy);
35
+ if (!hostname) {
36
+ return resolve(false);
37
+ }
38
+
39
+ dns.lookup(hostname, (proxyErr) => {
40
+ resolve(proxyErr == null);
41
+ });
42
+ });
43
+ });
44
+ }
@@ -0,0 +1,17 @@
1
+ // Modified from: https://github.com/vercel/next.js/blob/2425f4703c4c6164cecfdb6aa8f80046213f0cc6/packages/create-next-app/helpers/is-writeable.ts
2
+ import fs from 'fs';
3
+
4
+ import { getErrorCode } from './get-error-code.js';
5
+
6
+ export async function isWriteable(directory: string): Promise<boolean> {
7
+ try {
8
+ await fs.promises.access(directory, (fs.constants || fs).W_OK);
9
+ return true;
10
+ } catch (error) {
11
+ if (getErrorCode(error) === 'EACCES') {
12
+ return false;
13
+ } else {
14
+ throw error;
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,6 @@
1
+ // Modified from: https://github.com/vercel/next.js/blob/2425f4703c4c6164cecfdb6aa8f80046213f0cc6/packages/create-next-app/helpers/make-dir.ts
2
+ import fs from 'fs';
3
+
4
+ export async function makeDir(root: string, options = { recursive: true }): Promise<void> {
5
+ await fs.promises.mkdir(root, options);
6
+ }
@@ -0,0 +1,142 @@
1
+ // Modified from: https://github.com/vercel/next.js/blob/2425f4703c4c6164cecfdb6aa8f80046213f0cc6/packages/create-next-app/helpers/examples.ts
2
+ import chalk from 'chalk';
3
+ import got from 'got';
4
+ import tar from 'tar';
5
+ import { Stream } from 'stream';
6
+ import { promisify } from 'util';
7
+ import { rm, readdir } from 'fs/promises';
8
+ import path from 'path';
9
+ import { getErrorCode } from './get-error-code.js';
10
+
11
+ const pipeline = promisify(Stream.pipeline);
12
+
13
+ export type RepoInfo = {
14
+ username: string;
15
+ name: string;
16
+ branch: string;
17
+ filePath: string;
18
+ };
19
+
20
+ export async function isUrlOk(url: string): Promise<boolean> {
21
+ let res;
22
+ try {
23
+ res = await got.head(url);
24
+ } catch (e) {
25
+ return false;
26
+ }
27
+ return res.statusCode === 200;
28
+ }
29
+
30
+ // https://stackoverflow.com/a/3561711/627729
31
+ function escapeRegex(s: string) {
32
+ // eslint-disable-next-line no-useless-escape
33
+ return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
34
+ }
35
+
36
+ export async function getRepoInfo(url: URL, samplePath?: string): Promise<RepoInfo> {
37
+ const [, username, name, t, _branch, ...file] = url.pathname.split('/');
38
+ const filePath = samplePath ? samplePath.replace(/^\//, '') : file.join('/');
39
+
40
+ // Support repos whose entire purpose is to be a Temporal sample, e.g.
41
+ // https://github.com/:username/:my-cool-temporal-sample-repo
42
+ if (t === undefined) {
43
+ const repo = `https://api.github.com/repos/${username}/${name}`;
44
+ let infoResponse;
45
+
46
+ try {
47
+ // https://github.com/sindresorhus/got/blob/main/documentation/3-streams.md#response-1
48
+ infoResponse = await got(repo);
49
+ } catch (error) {
50
+ throw new Error(`Unable to fetch ${repo}`);
51
+ }
52
+
53
+ if (infoResponse.statusCode !== 200) {
54
+ throw new Error(`Unable to fetch ${repo} — Code ${infoResponse.statusCode}: ${infoResponse.statusMessage}`);
55
+ }
56
+
57
+ const info = JSON.parse(infoResponse.body);
58
+ return { username, name, branch: info['default_branch'], filePath };
59
+ }
60
+
61
+ // If samplePath is available, the branch name takes the entire path
62
+ const branch = samplePath
63
+ ? `${_branch}/${file.join('/')}`.replace(new RegExp(`/${escapeRegex(filePath)}|/$`), '')
64
+ : _branch;
65
+
66
+ if (username && name && branch && t === 'tree') {
67
+ return { username, name, branch, filePath };
68
+ } else {
69
+ throw new Error(`Unable to parse URL: ${url} and sample path: ${samplePath}`);
70
+ }
71
+ }
72
+
73
+ export async function checkForPackageJson({ username, name, branch, filePath }: RepoInfo): Promise<void> {
74
+ const contentsUrl = `https://api.github.com/repos/${username}/${name}/contents`;
75
+ const packagePath = `${filePath ? `/${filePath}` : ''}/package.json`;
76
+
77
+ const fullUrl = contentsUrl + packagePath + `?ref=${branch}`;
78
+
79
+ try {
80
+ const response = await got.head(fullUrl);
81
+ if (response.statusCode !== 200) {
82
+ throw response;
83
+ }
84
+ } catch (e) {
85
+ console.error(e);
86
+ throw new Error(
87
+ `Could not locate a package.json at ${chalk.red(
88
+ `"${fullUrl}"`
89
+ )}.\nPlease check that the repository is a Temporal TypeScript SDK template and try again.`
90
+ );
91
+ }
92
+ }
93
+
94
+ export function hasSample(name: string): Promise<boolean> {
95
+ return isUrlOk(
96
+ `https://api.github.com/repos/temporalio/samples-typescript/contents/${encodeURIComponent(name)}/package.json`
97
+ );
98
+ }
99
+
100
+ export async function downloadAndExtractRepo(
101
+ root: string,
102
+ { username, name, branch, filePath }: RepoInfo
103
+ ): Promise<void> {
104
+ const archiveUrl = `https://codeload.github.com/${username}/${name}/tar.gz/${branch}`;
105
+ const archivePath = `${name}-${branch}${filePath ? `/${filePath}` : ''}`;
106
+
107
+ await pipeline(
108
+ got.stream(archiveUrl),
109
+ tar.extract({ cwd: root, strip: filePath ? filePath.split('/').length + 1 : 1 }, [archivePath])
110
+ );
111
+
112
+ const files = await readdir(root);
113
+ const pipelineFailed = files.length === 0;
114
+ if (pipelineFailed) {
115
+ console.error(
116
+ 'We were unable to download and extract the provided project.\n',
117
+ `Archive URL: ${archiveUrl}\n`,
118
+ `Archive path: ${archivePath}\n`,
119
+ `Sometimes this is due to the repo name changing. If that's the case, try using the new repo URL.`
120
+ );
121
+ process.exit(1);
122
+ }
123
+ }
124
+
125
+ export async function downloadAndExtractSample(root: string, name: string): Promise<void> {
126
+ if (name === '__internal-testing-retry') {
127
+ throw new Error('This is an internal sample for testing the CLI.');
128
+ }
129
+
130
+ await pipeline(
131
+ got.stream('https://codeload.github.com/temporalio/samples-typescript/tar.gz/main'),
132
+ tar.extract({ cwd: root, strip: 2 }, [`samples-typescript-main/${name}`])
133
+ );
134
+
135
+ try {
136
+ await rm(path.join(root, `.npmrc`));
137
+ } catch (e) {
138
+ if (getErrorCode(e) !== 'ENOENT') {
139
+ throw e;
140
+ }
141
+ }
142
+ }
@@ -0,0 +1,14 @@
1
+ import glob from 'glob';
2
+ import path from 'path';
3
+ import { readFile, writeFile } from 'fs/promises';
4
+
5
+ export async function stripSnipComments(root: string): Promise<void> {
6
+ const files = glob.sync('**/*.ts', { cwd: root });
7
+ await Promise.all(
8
+ files.map(async (file) => {
9
+ const filePath = path.join(root, file);
10
+ const fileString = await readFile(filePath, 'utf8');
11
+ await writeFile(filePath, fileString.replace(/ *\/\/ @@@SNIP.+\n/g, ''));
12
+ })
13
+ );
14
+ }
@@ -0,0 +1,38 @@
1
+ // https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options
2
+ import { ChildProcess, spawn as origSpawn, SpawnOptions } from 'child_process';
3
+
4
+ export class ChildProcessError extends Error {
5
+ public readonly name = 'ChildProcessError';
6
+ public command?: string;
7
+ public args?: ReadonlyArray<string>;
8
+
9
+ constructor(message: string, public readonly code: number | null, public readonly signal: string | null) {
10
+ super(message);
11
+ }
12
+ }
13
+
14
+ export async function spawn(command: string, args?: ReadonlyArray<string>, options?: SpawnOptions): Promise<void> {
15
+ try {
16
+ // Workaround @types/node - avoid choosing overloads per options.stdio variants
17
+ await waitOnChild(options === undefined ? origSpawn(command, args) : origSpawn(command, args || [], options));
18
+ } catch (err) {
19
+ if (err instanceof ChildProcessError) {
20
+ err.command = command;
21
+ err.args = args;
22
+ }
23
+ throw err;
24
+ }
25
+ }
26
+
27
+ export async function waitOnChild(child: ChildProcess): Promise<void> {
28
+ return new Promise((resolve, reject) => {
29
+ child.on('exit', (code, signal) => {
30
+ if (code === 0) {
31
+ resolve();
32
+ } else {
33
+ reject(new ChildProcessError('Process failed', code, signal));
34
+ }
35
+ });
36
+ child.on('error', reject);
37
+ });
38
+ }
@@ -0,0 +1,17 @@
1
+ // Modified from: https://github.com/vercel/next.js/blob/2425f4703c4c6164cecfdb6aa8f80046213f0cc6/packages/create-next-app/helpers/validate-pkg.ts
2
+ import validateProjectName from 'validate-npm-package-name';
3
+
4
+ export function validateNpmName(name: string): {
5
+ valid: boolean;
6
+ problems?: string[];
7
+ } {
8
+ const nameValidation = validateProjectName(name);
9
+ if (nameValidation.validForNewPackages) {
10
+ return { valid: true };
11
+ }
12
+
13
+ return {
14
+ valid: false,
15
+ problems: [...(nameValidation.errors || []), ...(nameValidation.warnings || [])],
16
+ };
17
+ }
package/src/index.ts ADDED
@@ -0,0 +1,217 @@
1
+ // Modified from: https://github.com/vercel/next.js/blob/2425f4703c4c6164cecfdb6aa8f80046213f0cc6/packages/create-next-app/index.ts
2
+ import chalk from 'chalk';
3
+ import dedent from 'dedent';
4
+ import { Command } from 'commander';
5
+ import path from 'path';
6
+ import prompts from 'prompts';
7
+ import checkForUpdate from 'update-check';
8
+
9
+ import { createApp } from './create-project.js';
10
+ import { validateNpmName } from './helpers/validate-pkg.js';
11
+ import { fetchSamples } from './helpers/fetch-samples.js';
12
+ import packageJson from './pkg.js';
13
+
14
+ const program = new Command(packageJson.name)
15
+ .version(packageJson.version, '-v, --version', 'Print the version and exit')
16
+ .arguments('[project-directory]')
17
+ .usage(`${chalk.green('[project-directory]')} [options]`)
18
+ .option(
19
+ '-s, --sample <name|github-url>',
20
+ dedent`
21
+ Which sample to bootstrap the project with. You can use the name of a sample
22
+ from https://github.com/temporalio/samples-typescript or use a GitHub URL.
23
+ The URL can have a branch and/or subdirectory—for example:
24
+ https://github.com/temporalio/samples-typescript/tree/next/hello-world
25
+ `
26
+ )
27
+ .option(
28
+ '--sample-path <path-to-sample>',
29
+ dedent`
30
+ In a rare case, your GitHub URL might contain a branch name with
31
+ a slash (e.g. bug/fix-1) and the path to the sample (e.g. foo/bar).
32
+ In this case, you must specify the path to the sample separately:
33
+ --sample-path foo/bar
34
+ `
35
+ )
36
+ .option(
37
+ '-l, --list-samples',
38
+ dedent`
39
+ Print available sample projects and exit
40
+ `
41
+ )
42
+ .option(
43
+ '--use-yarn',
44
+ dedent`
45
+ Use yarn instead of npm
46
+ `
47
+ )
48
+ .option(
49
+ '--git-init',
50
+ dedent`
51
+ Initialize a git repository
52
+ `
53
+ )
54
+ .option(
55
+ '--no-git-init',
56
+ dedent`
57
+ Skip git repository initialization
58
+ `
59
+ )
60
+ .option(
61
+ '--sdk-version <version>',
62
+ dedent`
63
+ Specify which version of the @temporalio/* npm packages to use
64
+ `
65
+ )
66
+ .allowUnknownOption()
67
+ .parse(process.argv);
68
+
69
+ interface Options {
70
+ useYarn?: boolean;
71
+ gitInit?: boolean;
72
+ listSamples?: boolean;
73
+ sample?: string;
74
+ samplePath?: string;
75
+ sdkVersion?: string;
76
+ }
77
+
78
+ let opts: Options;
79
+
80
+ async function start(): Promise<void> {
81
+ opts = program.opts();
82
+ if (opts.listSamples) {
83
+ const samples = await fetchSamples();
84
+ console.log(`Available samples:\n\n${samples.join('\n')}\n`);
85
+ return;
86
+ }
87
+
88
+ let projectPath = program.args[0];
89
+
90
+ if (typeof projectPath === 'string') {
91
+ projectPath = projectPath.trim();
92
+ }
93
+
94
+ if (!projectPath) {
95
+ const res = await prompts({
96
+ type: 'text',
97
+ name: 'path',
98
+ message: 'What is your project named?',
99
+ initial: 'my-temporal',
100
+ validate: (name) => {
101
+ const validation = validateNpmName(path.basename(path.resolve(name)));
102
+ if (validation.valid) {
103
+ return true;
104
+ }
105
+ return 'Invalid project name: ' + validation.problems?.[0];
106
+ },
107
+ });
108
+
109
+ if (typeof res.path === 'string') {
110
+ projectPath = res.path.trim();
111
+ }
112
+ }
113
+
114
+ if (!projectPath) {
115
+ console.error();
116
+ console.error('Please specify the project directory:');
117
+ console.error(` ${chalk.cyan(program.name())} ${chalk.green('<project-directory>')}`);
118
+ console.error();
119
+ console.error('For example:');
120
+ console.error(` ${chalk.cyan(program.name())} ${chalk.green('my-temporal-project')}`);
121
+ console.error();
122
+ console.error(`Run ${chalk.cyan(`${program.name()} --help`)} to see all options.`);
123
+ process.exit(1);
124
+ }
125
+
126
+ const resolvedProjectPath = path.resolve(projectPath);
127
+ const projectName = path.basename(resolvedProjectPath);
128
+
129
+ const { valid, problems } = validateNpmName(projectName);
130
+ if (!valid) {
131
+ console.error(
132
+ `Could not create a project called ${chalk.red(`"${projectName}"`)} because of npm naming restrictions:`
133
+ );
134
+
135
+ problems?.forEach((p) => console.error(` ${chalk.red.bold('*')} ${p}`));
136
+ process.exit(1);
137
+ }
138
+
139
+ let sample = opts.sample;
140
+ if (!sample) {
141
+ const samples = await fetchSamples();
142
+ const choices = samples.map((sample) => ({ title: sample, value: sample }));
143
+
144
+ const res = await prompts({
145
+ type: 'select',
146
+ name: 'sample',
147
+ message: `Which sample would you like to use?`,
148
+ choices,
149
+ initial: samples.indexOf('hello-world'),
150
+ });
151
+
152
+ if (typeof res.sample === 'string') {
153
+ sample = res.sample;
154
+ }
155
+ }
156
+
157
+ if (!sample) {
158
+ console.error();
159
+ console.error('Please specify which sample:');
160
+ console.error(` ${chalk.cyan(program.name())} --sample ${chalk.green('<name|github-url>')}`);
161
+ console.error();
162
+ console.error('For example:');
163
+ console.error(` ${chalk.cyan(program.name())} --sample ${chalk.green('hello-world')}`);
164
+ console.error();
165
+ console.error(`Run ${chalk.cyan(`${program.name()} --help`)} to see all options.`);
166
+ process.exit(1);
167
+ }
168
+
169
+ await createApp({
170
+ appPath: resolvedProjectPath,
171
+ useYarn: !!opts.useYarn,
172
+ gitInit: opts.gitInit,
173
+ sdkVersion: opts.sdkVersion,
174
+ sample: sample.trim(),
175
+ samplePath: typeof opts.samplePath === 'string' ? opts.samplePath.trim() : undefined,
176
+ });
177
+ }
178
+
179
+ const update = checkForUpdate(packageJson).catch(() => null);
180
+
181
+ async function notifyUpdate(): Promise<void> {
182
+ try {
183
+ const res = await update;
184
+ if (res?.latest) {
185
+ console.log();
186
+ console.log(chalk.yellow.bold('A new version of `@temporalio/create` is available!'));
187
+ console.log(
188
+ 'You can update by running: ' +
189
+ chalk.cyan(opts.useYarn ? 'yarn global add @temporalio/create' : 'npm i -g @temporalio/create')
190
+ );
191
+ console.log();
192
+ }
193
+ process.exit();
194
+ } catch {
195
+ // ignore error
196
+ }
197
+ }
198
+
199
+ export function run(): void {
200
+ start()
201
+ .then(notifyUpdate)
202
+ .catch(async (reason) => {
203
+ console.log();
204
+ console.log('Aborting installation.');
205
+ if (reason.command) {
206
+ console.log(` ${chalk.cyan(reason.command)} has failed.`);
207
+ } else {
208
+ console.log(chalk.red('Unexpected error. Please report it as a bug:'));
209
+ console.log(reason);
210
+ }
211
+ console.log();
212
+
213
+ await notifyUpdate();
214
+
215
+ process.exit(1);
216
+ });
217
+ }