create-gasket-app 7.4.12 → 7.4.14

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.
package/EXAMPLES.md ADDED
@@ -0,0 +1,48 @@
1
+ # Create Gasket App Examples
2
+
3
+ This document provides working examples for interfaces and utilities from the `create-gasket-app` package.
4
+
5
+ ## Primary Usage (CLI)
6
+
7
+ The main interface for `create-gasket-app` is the command line:
8
+
9
+ ```bash
10
+ # Basic app creation
11
+ npx create-gasket-app@latest my-app
12
+
13
+ # With presets
14
+ npx create-gasket-app@latest my-app --presets @gasket/preset-nextjs
15
+
16
+ # With package manager selection
17
+ npx create-gasket-app@latest my-app --package-manager pnpm
18
+
19
+ # With configuration
20
+ npx create-gasket-app@latest my-app --config '{"typescript": true}'
21
+
22
+ # With config file
23
+ npx create-gasket-app@latest my-app --config-file ./create-config.json
24
+
25
+ # Skip prompts (for CI)
26
+ npx create-gasket-app@latest my-app --presets @gasket/preset-nextjs --no-prompts
27
+ ```
28
+
29
+ ## Templates
30
+
31
+ Templates provide complete Gasket applications ready to use:
32
+
33
+ ```bash
34
+ # Use an npm template package
35
+ npx create-gasket-app@latest my-app --template @gasket/template-nextjs-pages-js
36
+
37
+ # Use a versioned template
38
+ npx create-gasket-app@latest my-app --template @gasket/template-api@^2.0.0
39
+
40
+ # Use a tagged template
41
+ npx create-gasket-app@latest my-app --template @gasket/template-nextjs@beta
42
+
43
+ # Use a local template during development
44
+ npx create-gasket-app@latest my-app --template-path ./my-gasket-template
45
+
46
+ # Use local template with absolute path
47
+ npx create-gasket-app@latest my-app --template-path /path/to/my-template
48
+ ```
package/README.md CHANGED
@@ -43,6 +43,10 @@ Options:
43
43
  -p, --presets [presets] Initial Gasket preset(s) to use.
44
44
  Can be set as short name with version (e.g. --presets nextjs@^1.0.0)
45
45
  Or other (multiple) custom presets (e.g. --presets my-gasket-preset@1.0.0.beta-1,nextjs@^1.0.0)
46
+ --template [template] Selects which template you would like to use during
47
+ installation. (e.g. --template @gasket/template-nextjs-pages-js)
48
+ --template-path [template-path] (INTERNAL) Path to a local template package. Can be absolute
49
+ or relative to the current working directory.
46
50
  --package-manager [package-manager] Selects which package manager you would like to use during
47
51
  installation. (e.g. --package-manager yarn)
48
52
  -r, --require [require] Require module(s) before Gasket is initialized
@@ -51,6 +55,48 @@ Options:
51
55
  -h, --help display help for command
52
56
  ```
53
57
 
58
+ #### Templates
59
+
60
+ Templates provide a complete Gasket application ready to use, unlike presets which build apps through a plugin-based creation process.
61
+
62
+ ##### Using Templates
63
+
64
+ ```bash
65
+ # Use an official template from npm
66
+ npx create-gasket-app@latest my-app --template @gasket/template-nextjs-pages-js
67
+
68
+ # Use a versioned template
69
+ npx create-gasket-app@latest my-app --template @gasket/template-api@^2.0.0
70
+
71
+ # Use a local template (development)
72
+ npx create-gasket-app@latest my-app --template-path ./path/to/my-template
73
+
74
+ # Use a template with tag
75
+ npx create-gasket-app@latest my-app --template @gasket/template-nextjs-pages-js@beta
76
+ ```
77
+
78
+ ##### Template Structure
79
+
80
+ Templates are npm packages that follow this structure:
81
+
82
+ ```
83
+ @gasket/template-example/
84
+ ├── package.json
85
+ ├── template/
86
+ │ ├── package.json # App's package.json
87
+ │ ├── gasket.js # App's gasket config
88
+ │ ├── src/ # App source files
89
+ │ └── ... # Other app files
90
+ └── README.md
91
+ ```
92
+
93
+ When using templates:
94
+ - The entire `template/` directory is copied to your new app
95
+ - Template dependencies are installed with `npm ci`
96
+ - No preset processing or plugin creation flows are run
97
+
98
+ **Note:** Templates currently require npm as they come with `package-lock.json` files.
99
+
54
100
  #### Package Managers
55
101
 
56
102
  With `create-gasket-app`, you can choose either [npm] or [yarn] as the package
@@ -6,6 +6,10 @@ import { rm } from 'fs/promises';
6
6
  import { makeGasket } from '@gasket/core';
7
7
  import globalPrompts from '../scaffold/actions/global-prompts.js';
8
8
  import loadPreset from '../scaffold/actions/load-preset.js';
9
+ import loadTemplate from '../scaffold/actions/load-template.js';
10
+ import copyTemplate from '../scaffold/actions/copy-template.js';
11
+ import customizeTemplate from '../scaffold/actions/customize-template.js';
12
+ import installTemplateDep from '../scaffold/actions/install-template-deps.js';
9
13
  import presetPromptHooks from '../scaffold/actions/preset-prompt-hooks.js';
10
14
  import presetConfigHooks from '../scaffold/actions/preset-config-hooks.js';
11
15
  import promptHooks from '../scaffold/actions/prompt-hooks.js';
@@ -19,6 +23,7 @@ import installModules from '../scaffold/actions/install-modules.js';
19
23
  import linkModules from '../scaffold/actions/link-modules.js';
20
24
  import postCreateHooks from '../scaffold/actions/post-create-hooks.js';
21
25
  import printReport from '../scaffold/actions/print-report.js';
26
+ import gitInit from '../scaffold/actions/git-init.js';
22
27
 
23
28
  /**
24
29
  * Parses comma separated option input to array
@@ -45,6 +50,17 @@ const createCommand = {
45
50
  Or other (multiple) custom presets (e.g. --presets my-gasket-preset@1.0.0.beta-1,nextjs@^1.0.0)`,
46
51
  parse: commasToArray
47
52
  },
53
+ {
54
+ name: 'template',
55
+ description: `Selects which template you would like to use during
56
+ installation. (e.g. --template @gasket/template-nextjs-pages-js)`
57
+ },
58
+ {
59
+ name: 'template-path',
60
+ description: `(INTERNAL) Path to a local template package. Can be absolute
61
+ or relative to the current working directory.`,
62
+ hidden: true
63
+ },
48
64
  {
49
65
  name: 'package-manager',
50
66
  description: `Selects which package manager you would like to use during
@@ -90,6 +106,87 @@ const createCommand = {
90
106
  ]
91
107
  };
92
108
 
109
+ /**
110
+ * validateOption - We require at least one of the options to be provided
111
+ * @param {import('../internal.js').PartialCreateContext} context
112
+ * @returns {import('../internal.js').PartialCreateContext}
113
+ */
114
+ function validateOptions(context) {
115
+ const hasOption = (context) => Boolean(context.template || context.templatePath || context.rawPresets.length || context.localPresets.length);
116
+
117
+ if (!hasOption(context)) {
118
+ console.warn('Warning: At least one of the options is required: --template, --template-path, --presets, --preset-path');
119
+ process.exit(1);
120
+ }
121
+
122
+ return context;
123
+ }
124
+
125
+ /**
126
+ * handleTemplate - Handles the template creation
127
+ * @param {import('../internal.js').PartialCreateContext} context
128
+ * @returns {Promise<void>}
129
+ */
130
+ async function handleTemplate(context) {
131
+ await mkDir({ context });
132
+ await loadTemplate({ context });
133
+ await copyTemplate({ context });
134
+ await customizeTemplate({ context });
135
+ await installTemplateDep({ context });
136
+ await gitInit({ context });
137
+ printReport({ context });
138
+ return;
139
+ }
140
+
141
+ /**
142
+ * handlePresets - Handles the preset creation
143
+ * @param {import('../internal.js').PartialCreateContext} context
144
+ * @returns {Promise<void>}
145
+ */
146
+ async function handlePresets(context) {
147
+ await loadPreset({ context });
148
+ await globalPrompts({ context });
149
+
150
+ const presetGasket = makeGasket({
151
+ plugins: context.presets
152
+ });
153
+
154
+ await presetPromptHooks({ gasket: presetGasket, context });
155
+ await presetConfigHooks({ gasket: presetGasket, context });
156
+
157
+ const pluginGasket = makeGasket({
158
+ ...context.presetConfig,
159
+ plugins: context.presetConfig.plugins.concat(context.presets)
160
+ });
161
+
162
+ await promptHooks({ gasket: pluginGasket, context });
163
+ await mkDir({ context });
164
+ await setupPkg({ context });
165
+ await createHooks({ gasket: pluginGasket, context });
166
+ await writePkg({ context });
167
+ await generateFiles({ context });
168
+ await writeGasketConfig({ context });
169
+ await installModules({ context });
170
+ await linkModules({ context });
171
+ await postCreateHooks({ gasket: pluginGasket, context });
172
+ if (context.tmpDir) await rm(context.tmpDir, { recursive: true });
173
+ printReport({ context });
174
+ }
175
+
176
+ /**
177
+ * handleCreate - Handles the create creation
178
+ * @param {import('../internal.js').PartialCreateContext} context
179
+ * @returns {Promise<void>}
180
+ */
181
+ async function handleCreate(context) {
182
+ const { template, templatePath } = context;
183
+ if (template || templatePath) {
184
+ await handleTemplate(context);
185
+ } else {
186
+ await handlePresets(context);
187
+ }
188
+ }
189
+
93
190
  /**
94
191
  * createCommand action
95
192
  * @type {import('../index.js').createCommandAction}
@@ -98,39 +195,10 @@ createCommand.action = async function run(appname, options, command) {
98
195
  // eslint-disable-next-line no-process-env
99
196
  process.env.GASKET_ENV = 'create';
100
197
  const context = makeCreateContext([appname], options);
101
- const { rawPresets, localPresets } = context;
198
+ validateOptions(context);
102
199
 
103
200
  try {
104
- await globalPrompts({ context });
105
-
106
- if (rawPresets.length || localPresets.length) {
107
- await loadPreset({ context });
108
-
109
- const presetGasket = makeGasket({
110
- plugins: context.presets
111
- });
112
-
113
- await presetPromptHooks({ gasket: presetGasket, context });
114
- await presetConfigHooks({ gasket: presetGasket, context });
115
- }
116
-
117
- const pluginGasket = makeGasket({
118
- ...context.presetConfig,
119
- plugins: context.presetConfig.plugins.concat(context.presets)
120
- });
121
-
122
- await promptHooks({ gasket: pluginGasket, context });
123
- await mkDir({ context });
124
- await setupPkg({ context });
125
- await createHooks({ gasket: pluginGasket, context });
126
- await writePkg({ context });
127
- await generateFiles({ context });
128
- await writeGasketConfig({ context });
129
- await installModules({ context });
130
- await linkModules({ context });
131
- await postCreateHooks({ gasket: pluginGasket, context });
132
- if (context.tmpDir) await rm(context.tmpDir, { recursive: true });
133
- printReport({ context });
201
+ await handleCreate(context);
134
202
  } catch (err) {
135
203
  // eslint-disable-next-line no-console
136
204
  console.error(chalk.red('Exiting with errors.'));
package/lib/index.d.ts CHANGED
@@ -47,6 +47,8 @@ interface CommandOption {
47
47
 
48
48
  export interface CreateCommandOptions {
49
49
  presets?: string[];
50
+ template?: string;
51
+ templatePath?: string;
50
52
  npmLink?: string[];
51
53
  presetPath?: string[];
52
54
  packageManager?: string;
@@ -459,6 +461,18 @@ export interface CreateContext {
459
461
  /** temporary directory */
460
462
  tmpDir: string;
461
463
 
464
+ /** template to use for app creation */
465
+ template?: string;
466
+
467
+ /** path to local template package */
468
+ templatePath?: string;
469
+
470
+ /** resolved template directory path */
471
+ templateDir?: string;
472
+
473
+ /** resolved template name for display */
474
+ templateName?: string;
475
+
462
476
  /** Default to object w/empty plugins array to be populated by `presetConfig` hook */
463
477
  presetConfig: GasketConfigDefinition;
464
478
 
package/lib/internal.d.ts CHANGED
@@ -104,6 +104,7 @@ export function promptForTestPlugin(
104
104
  export function allowExtantOverwriting(context: PartialCreateContext, prompt: CreatePrompt): Promise<void>;
105
105
  export function globalPrompts(params: { context: PartialCreateContext }): Promise<void>;
106
106
  export function loadPresets(params: { context: PartialCreateContext }): Promise<void>;
107
+
107
108
  export function presetPromptHooks(params: { gasket?: Gasket; context: PartialCreateContext }): Promise<void>;
108
109
  export function presetConfigHooks(params: { gasket?: Gasket; context: PartialCreateContext }): Promise<void>;
109
110
  export function promptHooks(params: { gasket?: Gasket; context: PartialCreateContext }): Promise<void>;
@@ -124,6 +125,16 @@ export type Descriptior = {
124
125
  from: string;
125
126
  overrides?: string;
126
127
  }
128
+
129
+ /** Templates */
130
+ export function loadTemplate(params: { context: PartialCreateContext }): Promise<void>;
131
+ export function copyTemplate(params: { context: PartialCreateContext }): Promise<void>;
132
+ export function customizeTemplate(params: { context: PartialCreateContext }): Promise<void>;
133
+ export function installTemplateDeps(params: { context: PartialCreateContext }): Promise<void>;
134
+ export function gitInit(params: { context: PartialCreateContext }): Promise<void>;
135
+
136
+
137
+ /** Presets */
127
138
  export function generateFiles({ context, spinner }: { context: CreateContext, spinner: Ora }): Promise<void>;
128
139
  export function performGenerate(context: CreateContext, descriptors: Descriptior[]): boolean[];
129
140
  export function getDescriptors(context: CreateContext): Promise<Descriptior[]>;
@@ -0,0 +1,22 @@
1
+ import path from 'path';
2
+ import { withSpinner } from '../with-spinner.js';
3
+ import { cp, readdir } from 'fs/promises';
4
+
5
+ const EXCLUDED_FILES = ['node_modules'];
6
+
7
+ /** @type {import('../../internal.d.ts').copyTemplate} */
8
+ async function copyTemplate({ context }) {
9
+ if (!context.templateDir) return;
10
+
11
+ const entries = await readdir(context.templateDir, { withFileTypes: true });
12
+ const filesToCopy = entries.filter(entry => !EXCLUDED_FILES.includes(entry.name));
13
+
14
+ for (const entry of filesToCopy) {
15
+ const sourcePath = path.join(context.templateDir, entry.name);
16
+ const destPath = path.join(context.dest, entry.name);
17
+ await cp(sourcePath, destPath, { recursive: true });
18
+ context.generatedFiles.add(path.relative(context.cwd, destPath));
19
+ }
20
+ }
21
+
22
+ export default withSpinner('Copy template files', copyTemplate);
@@ -0,0 +1,92 @@
1
+ import path from 'path';
2
+ import { withSpinner } from '../with-spinner.js';
3
+ import { readFile, writeFile } from 'fs/promises';
4
+
5
+ /** @type {import('../../internal.d.ts').customizeTemplate} */
6
+ async function customizeTemplate({ context }) {
7
+ if (!context.templateDir || !context.appName) return;
8
+
9
+ await Promise.all([
10
+ updatePackageJson(context),
11
+ updateReadme(context),
12
+ updateTemplateFiles(context)
13
+ ]);
14
+ }
15
+
16
+ /**
17
+ * Updates package.json name field with app name
18
+ * @param {object} context - Gasket context
19
+ * @returns {Promise<void>}
20
+ */
21
+ async function updatePackageJson(context) {
22
+ const packageJsonPath = path.join(context.dest, 'package.json');
23
+
24
+ try {
25
+ const content = await readFile(packageJsonPath, 'utf8');
26
+ const packageJson = JSON.parse(content);
27
+
28
+ packageJson.name = context.appName;
29
+
30
+ await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
31
+ } catch (error) {
32
+ // Ignore if package.json doesn't exist or is malformed
33
+ if (error.code !== 'ENOENT') {
34
+ console.warn(`Warning: Could not update package.json: ${error.message}`);
35
+ }
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Updates README.md with app name placeholders replaced
41
+ * @param {object} context - Gasket context
42
+ * @returns {Promise<void>}
43
+ */
44
+ async function updateReadme(context) {
45
+ const readmePath = path.join(context.dest, 'README.md');
46
+
47
+ try {
48
+ const content = await readFile(readmePath, 'utf8');
49
+
50
+ // Replace all {appName} placeholders with actual app name
51
+ const updatedContent = content.replace(/{{{appName}}}/g, context.appName);
52
+
53
+ await writeFile(readmePath, updatedContent);
54
+ } catch (error) {
55
+ // Ignore if README.md doesn't exist
56
+ if (error.code !== 'ENOENT') {
57
+ console.warn(`Warning: Could not update README.md: ${error.message}`);
58
+ }
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Updates template files with app name placeholders replaced
64
+ * @param {object} context - Gasket context
65
+ * @returns {Promise<void>}
66
+ */
67
+ async function updateTemplateFiles(context) {
68
+ const templateFilePaths = [
69
+ path.join(context.dest, 'pages/index.tsx'),
70
+ path.join(context.dest, 'app/page.tsx')
71
+ ];
72
+
73
+ const updatePromises = templateFilePaths.map(async (filePath) => {
74
+ try {
75
+ const content = await readFile(filePath, 'utf8');
76
+
77
+ // Replace all {appName} placeholders with actual app name
78
+ const updatedContent = content.replace(/{{{appName}}}/g, context.appName);
79
+
80
+ await writeFile(filePath, updatedContent);
81
+ } catch (error) {
82
+ // Ignore if file doesn't exist (not all templates have these files)
83
+ if (error.code !== 'ENOENT') {
84
+ console.warn(`Warning: Could not update ${filePath}: ${error.message}`);
85
+ }
86
+ }
87
+ });
88
+
89
+ await Promise.all(updatePromises);
90
+ }
91
+
92
+ export default withSpinner('Customize template files', customizeTemplate);
@@ -0,0 +1,15 @@
1
+ import { withSpinner } from '../with-spinner.js';
2
+ import { runShellCommand } from '@gasket/utils';
3
+
4
+ /**
5
+ * Initialize git repository, create main branch, and make initial commit
6
+ * @type {import('../../internal.d.js').gitInit}
7
+ */
8
+ async function gitInit({ context }) {
9
+ await runShellCommand('git', ['init'], { cwd: context.dest });
10
+ await runShellCommand('git', ['checkout', '-b', 'main'], { cwd: context.dest });
11
+ await runShellCommand('git', ['add', '.'], { cwd: context.dest });
12
+ await runShellCommand('git', ['commit', '-m', '🎉 Created new repository with create-gasket-app'], { cwd: context.dest });
13
+ }
14
+
15
+ export default withSpinner('Initialize git repo', gitInit);
@@ -0,0 +1,61 @@
1
+ import { withSpinner } from '../with-spinner.js';
2
+ import { PackageManager } from '@gasket/utils';
3
+ import { rm } from 'fs/promises';
4
+
5
+ /**
6
+ * Safely cleans up temporary directory
7
+ * @param {string} tmpDir - Temporary directory path
8
+ * @returns {Promise<void>}
9
+ */
10
+ async function cleanupTempDir(tmpDir) {
11
+ if (!tmpDir) return;
12
+
13
+ try {
14
+ await rm(tmpDir, { recursive: true });
15
+ } catch {
16
+ // Ignore cleanup errors
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Checks if an error is related to peer dependencies
22
+ * @param {Error & { stderr?: string }} error - The error to check
23
+ * @returns {boolean} True if error is peer dep related
24
+ */
25
+ function isPeerDependencyError(error) {
26
+ const errorMessage = (error.stderr || error.message || '').toLowerCase();
27
+ return errorMessage.includes('peer dep') ||
28
+ errorMessage.includes('peerinvalid') ||
29
+ errorMessage.includes('peer dependencies') ||
30
+ errorMessage.includes('eresolve');
31
+ }
32
+
33
+ /** @type {import('../../internal.d.ts').installTemplateDeps} */
34
+ async function installTemplateDeps({ context }) {
35
+ const destPkgManager = new PackageManager({
36
+ packageManager: 'npm',
37
+ dest: context.dest
38
+ });
39
+
40
+ try {
41
+ await destPkgManager.exec('ci');
42
+ await cleanupTempDir(context.tmpDir);
43
+ } catch (error) {
44
+ if (isPeerDependencyError(error)) {
45
+ console.warn('Peer dependency conflict detected, retrying with --legacy-peer-deps...');
46
+ try {
47
+ await destPkgManager.exec('ci', ['--legacy-peer-deps']);
48
+ await cleanupTempDir(context.tmpDir);
49
+ context.messages.push('Dependencies were installed with --legacy-peer-deps due to confilct');
50
+ } catch (retryError) {
51
+ await cleanupTempDir(context.tmpDir);
52
+ throw new Error(`Failed to install dependencies even with --legacy-peer-deps: ${retryError.message}`);
53
+ }
54
+ } else {
55
+ await cleanupTempDir(context.tmpDir);
56
+ throw error;
57
+ }
58
+ }
59
+ }
60
+
61
+ export default withSpinner('Install template dependencies', installTemplateDeps);
@@ -0,0 +1,110 @@
1
+ import path from 'path';
2
+ import os from 'os';
3
+ import { withSpinner } from '../with-spinner.js';
4
+ import { PackageManager } from '@gasket/utils';
5
+ import { mkdtemp, rm } from 'fs/promises';
6
+
7
+ /**
8
+ * Parses template string into name and version components
9
+ * @param {string} template - Template string (e.g., "@gasket/template-api@1.0.0")
10
+ * @returns {{ name: string, version: string }} Parsed template name and version
11
+ */
12
+ function parseTemplateNameAndVersion(template) {
13
+ // capture name and version, excluding `@`
14
+ const reNameVersion = /(^.[^@]+)[@](.+$)/i;
15
+ const [
16
+ name,
17
+ version = 'latest'
18
+ ] = template.split(reNameVersion).filter(Boolean);
19
+
20
+ return {
21
+ name,
22
+ version
23
+ };
24
+ }
25
+
26
+ /**
27
+ * Safely cleans up temporary directory
28
+ * @param {string} tmpDir - Temporary directory path
29
+ * @returns {Promise<void>}
30
+ */
31
+ async function cleanupTempDir(tmpDir) {
32
+ if (!tmpDir) return;
33
+
34
+ try {
35
+ await rm(tmpDir, { recursive: true });
36
+ } catch {
37
+ // Ignore cleanup errors
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Downloads and installs a remote template package
43
+ * @param {string} template - Template name with optional version
44
+ * @param {string} appName - App name for temp directory
45
+ * @returns {Promise<{ templateDir: string, templateName: string, tmpDir: string }>} Template installation details
46
+ */
47
+ async function installRemoteTemplate(template, appName) {
48
+ const tmpDir = await mkdtemp(path.join(os.tmpdir(), `gasket-template-${appName}`));
49
+ const modPath = path.join(tmpDir, 'node_modules');
50
+ const pkgManager = new PackageManager({
51
+ packageManager: 'npm',
52
+ dest: tmpDir
53
+ });
54
+
55
+ const { name, version } = parseTemplateNameAndVersion(template);
56
+
57
+ try {
58
+ await pkgManager.exec('install', [`${name}@${version}`]);
59
+ return {
60
+ templateDir: path.join(modPath, name, 'template'),
61
+ templateName: `${name}@${version}`,
62
+ tmpDir
63
+ };
64
+ } catch (err) {
65
+ await cleanupTempDir(tmpDir);
66
+
67
+ const errorMessage = err.stderr || err.message;
68
+ if (err.stderr?.includes('is not in this registry')) {
69
+ const message = `Template not found in registry: ${name}@${version}. ` +
70
+ 'Use npm_config_registry=<registry> to use privately scoped templates.';
71
+ throw new Error(message);
72
+ }
73
+ throw new Error(`Failed to install template ${name}@${version}: ${errorMessage}`);
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Gets template directory and name based on context
79
+ * @param {object} context - Gasket creation context
80
+ * @returns {Promise<{ templateDir: string, templateName: string, tmpDir?: string }>} Template details
81
+ */
82
+ async function getTemplateDetails(context) {
83
+ if (context.templatePath) {
84
+ return {
85
+ templateDir: path.resolve(context.templatePath, 'template'),
86
+ templateName: `local template at ${path.resolve(context.templatePath, 'template')}`
87
+ };
88
+ }
89
+
90
+ const result = await installRemoteTemplate(context.template, context.appName);
91
+ return {
92
+ templateDir: result.templateDir,
93
+ templateName: result.templateName,
94
+ tmpDir: result.tmpDir
95
+ };
96
+ }
97
+
98
+ /** @type {import('../../internal.d.ts').loadTemplate} */
99
+ async function loadTemplate({ context }) {
100
+ if (!context.template && !context.templatePath) return;
101
+
102
+ const { templateDir, templateName, tmpDir } = await getTemplateDetails(context);
103
+
104
+ // Store template information in context for other actions to use
105
+ context.templateDir = templateDir;
106
+ context.templateName = templateName;
107
+ context.tmpDir = tmpDir;
108
+ }
109
+
110
+ export default withSpinner('Loading template', loadTemplate);
@@ -74,6 +74,8 @@ export function makeCreateContext(argv = [], options = {}) {
74
74
  const appName = argv[0] || 'templated-app';
75
75
  const {
76
76
  presets = [],
77
+ template,
78
+ templatePath,
77
79
  npmLink = [],
78
80
  presetPath = [],
79
81
  packageManager,
@@ -107,6 +109,8 @@ export function makeCreateContext(argv = [], options = {}) {
107
109
  pkgLinks,
108
110
  localPresets,
109
111
  rawPresets,
112
+ template,
113
+ templatePath,
110
114
  messages: [],
111
115
  warnings: [],
112
116
  errors: [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-gasket-app",
3
- "version": "7.4.12",
3
+ "version": "7.4.14",
4
4
  "description": "starter pack for creating a gasket app",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,7 +9,8 @@
9
9
  "main": "lib/index.js",
10
10
  "types": "lib/index.d.ts",
11
11
  "files": [
12
- "lib"
12
+ "lib",
13
+ "EXAMPLES.md"
13
14
  ],
14
15
  "repository": "godaddy/gasket.git",
15
16
  "publishConfig": {
@@ -31,33 +32,31 @@
31
32
  "mkdirp": "^1.0.4",
32
33
  "ora": "^3.4.0",
33
34
  "semver": "^7.7.1",
34
- "@gasket/core": "^7.6.2",
35
- "@gasket/plugin-command": "^7.5.2",
36
- "@gasket/plugin-docs": "^7.4.7",
37
- "@gasket/plugin-docusaurus": "^7.4.5",
38
- "@gasket/plugin-dynamic-plugins": "^7.4.1",
39
- "@gasket/plugin-git": "^7.4.6",
40
- "@gasket/plugin-logger": "^7.3.7",
41
- "@gasket/plugin-metadata": "^7.5.3",
42
- "@gasket/request": "^7.5.3",
43
- "@gasket/utils": "^7.6.2"
35
+ "@gasket/core": "^7.6.3",
36
+ "@gasket/plugin-command": "^7.5.3",
37
+ "@gasket/plugin-docs": "^7.4.8",
38
+ "@gasket/plugin-docusaurus": "^7.4.6",
39
+ "@gasket/plugin-dynamic-plugins": "^7.4.2",
40
+ "@gasket/plugin-git": "^7.4.7",
41
+ "@gasket/plugin-logger": "^7.3.8",
42
+ "@gasket/plugin-metadata": "^7.5.4",
43
+ "@gasket/request": "^7.5.4",
44
+ "@gasket/utils": "^7.6.3"
44
45
  },
45
46
  "devDependencies": {
46
47
  "@typescript-eslint/parser": "^8.38.0",
48
+ "@vitest/coverage-v8": "^3.2.0",
47
49
  "dotenv": "^16.4.7",
48
- "jest": "^29.7.0",
49
- "@gasket/request": "^7.5.3"
50
- },
51
- "jest": {
52
- "transform": {}
50
+ "vitest": "^3.2.0",
51
+ "@gasket/request": "^7.5.4"
53
52
  },
54
53
  "scripts": {
55
54
  "lint": "eslint .",
56
55
  "lint:fix": "pnpm run lint --fix",
57
56
  "posttest": "pnpm run lint && pnpm run typecheck",
58
- "test": "cross-env NODE_OPTIONS='--unhandled-rejections=strict --experimental-vm-modules' jest",
59
- "test:coverage": "jest --coverage",
60
- "test:watch": "pnpm run test --watch",
57
+ "test": "vitest run --globals",
58
+ "test:coverage": "vitest run --coverage",
59
+ "test:watch": "vitest",
61
60
  "typecheck": "tsc",
62
61
  "typecheck:watch": "tsc --watch"
63
62
  }