handoff-app 1.0.4 → 1.0.6-canary.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.
package/Changelog.md CHANGED
@@ -6,6 +6,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to
7
7
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
8
 
9
+ ## \[1.0.6] - 2026-02-24
10
+
11
+ Version 1.0.4 fixed an issue when running Handoff in subpaths (eg.
12
+ https://mysite.com/folder/handoff). We discovered a minor bug in that fix that
13
+ causes some menu constructs to work poorly. This release fixes that issue.
14
+
15
+ ## \[1.0.5] - 2026-02-19
16
+
17
+ This is a minor improvement release that extends the quickstart released in the
18
+ 1.0.4. It extends the quickstart to work from the Handoff-CLI when installed
19
+ globally. You can use `handoff-app init` to initialize a new project.
20
+
9
21
  ## \[1.0.4] - 2026-02-19
10
22
 
11
23
  Bugfixes, UX improvements to the component page, improvements to react docgen,
package/README.md CHANGED
@@ -12,19 +12,18 @@ transform tokens into dev ready artifacts.
12
12
 
13
13
  ## Table of Contents
14
14
 
15
- - [What Is Handoff?](#what-is-handoff)
16
- - [How does it work](#how-does-it-work)
17
- - [Handoff is in Beta!](#handoff-is-in-beta)
18
- - [Requirements](#requirements)
19
- - [Get Started](#get-started)
20
- - [Table of Contents](#table-of-contents-1)
21
- - [Figma](#figma)
22
- - [Create a project](#create-a-project)
23
- - [Test fetch](#test-fetch)
24
- - [Further Reading](#further-reading)
25
- - [Maintainers](#maintainers)
26
- - [Contributing](#contributing)
27
- - [License](#license)
15
+ * [What Is Handoff?](#what-is-handoff)
16
+ * [How does it work](#how-does-it-work)
17
+ * [Handoff is in Beta!](#handoff-is-in-beta)
18
+ * [Requirements](#requirements)
19
+ * [Get Started](#get-started)
20
+ * [Figma](#figma)
21
+ * [Create a project](#create-a-project)
22
+ * [Test fetch](#test-fetch)
23
+ * [Further Reading](#further-reading)
24
+ * [Maintainers](#maintainers)
25
+ * [Contributing](#contributing)
26
+ * [License](#license)
28
27
 
29
28
  ## What Is Handoff?
30
29
 
@@ -35,19 +34,19 @@ between design and development.
35
34
 
36
35
  Handoff is a collection of 4 javascript tools:
37
36
 
38
- - **Figma Token Extraction** - A framework for extracting
37
+ * **Figma Token Extraction** - A framework for extracting
39
38
  standardized design foundations and components from Figma.
40
- - **Transformation Pipeline** - A set of transformers for producing SASS, CSS,
39
+ * **Transformation Pipeline** - A set of transformers for producing SASS, CSS,
41
40
  Style Dictionary, and preview snippets from that data.
42
- - **Documentation Web App** - A static, client side, Javascript web app that
41
+ * **Documentation Web App** - A static, client side, Javascript web app that
43
42
  renders live, working previews of your components, tokens and styles.
44
- - **Delivery Tools** - Easy build tooling and CI/CD wrapper that allows
43
+ * **Delivery Tools** - Easy build tooling and CI/CD wrapper that allows
45
44
  automation to render and ship the various deliverables as needed.
46
45
 
47
46
  ## How does it work?
48
47
 
49
48
  Handoff works by extracting design foundations and component data from
50
- [well-formed Figma libraries](https://www.figma.com/file/IGYfyraLDa0BpVXkxHY2tE/Starter-%5BV2%5D?node-id=0%3A1&t=iPYW37yDmNkJBt1t-0),
49
+ [well-formed Figma libraries](https://www.figma.com/file/IGYfyraLDa0BpVXkxHY2tE/Starter-%5BV2%5D?node-id=0%3A1\&t=iPYW37yDmNkJBt1t-0),
51
50
  storing them as JSON, and then transforming them into design tokens. Those
52
51
  design tokens are published as SASS and CSS variables.
53
52
 
@@ -56,23 +55,23 @@ any site using the [Bootstrap 5](https://getbootstrap.com/) frontend
56
55
  framework. If you use another framework, or custom CSS, you can easily write
57
56
  map files to connect the generated tokens with your site or application.
58
57
 
59
- - [Get Started](https://www.handoff.com/docs/quickstart)
60
- - [Requirements](https://www.handoff.com/docs/overview/requirements)
61
- - [Integrating Tokens](https://www.handoff.com/docs/tokens/integration)
62
- - [Customization](https://www.handoff.com/docs/customization)
58
+ * [Get Started](https://www.handoff.com/docs/quickstart)
59
+ * [Requirements](https://www.handoff.com/docs/overview/requirements)
60
+ * [Integrating Tokens](https://www.handoff.com/docs/tokens/integration)
61
+ * [Customization](https://www.handoff.com/docs/customization)
63
62
 
64
63
  Once Handoff extracts design tokens and variables, it builds a statically
65
64
  generated NextJS application that can be published to the web. This asset
66
65
  can be hosted on a static webhost (NGINX, s3/Cloudfront, Cloudflare pages
67
66
  etc).
68
67
 
69
- - [Tokens Overview](https://www.handoff.com/docs/tokens)
70
- - [Build Site](https://www.handoff.com/docs/tokens/publishing)
68
+ * [Tokens Overview](https://www.handoff.com/docs/tokens)
69
+ * [Build Site](https://www.handoff.com/docs/tokens/publishing)
71
70
 
72
71
  This pipeline from Figma to the Documentation Web app can be automated via CI/CD
73
72
  to provide automatic, up-to-date, easily readable developer documentation.
74
73
 
75
- - [CI/CD Integration](https://www.handoff.com/docs/guide/cicd)
74
+ * [CI/CD Integration](https://www.handoff.com/docs/guide/cicd)
76
75
 
77
76
  ## Handoff is in Beta!
78
77
 
@@ -82,64 +81,101 @@ a use case that isn't quite met.
82
81
 
83
82
  ## Requirements
84
83
 
85
- - A paid Figma account is required to publish the Figma file library
86
- - Node 18.17+
87
- - NPM 8+
84
+ * A paid Figma account is required to publish the Figma file library
85
+ * Node 18.17+
86
+ * NPM 8+
88
87
 
89
88
  ## Get Started
90
89
 
91
90
  ### Figma
92
91
 
93
- 1. Open the [Handoff Figma starter](https://www.figma.com/file/IGYfyraLDa0BpVXkxHY2tE/Starter-%5BV2%5D?node-id=0%3A1&t=iPYW37yDmNkJBt1t-0)
92
+ 1. Open the [Handoff Figma starter](https://www.figma.com/file/IGYfyraLDa0BpVXkxHY2tE/Starter-%5BV2%5D?node-id=0%3A1\&t=iPYW37yDmNkJBt1t-0)
94
93
  and duplicate this project to your account
95
94
 
96
95
  2. Publish components to the library
97
96
 
98
- - Click on the Figma logo at the top left
99
- - Click on `Libraries`
100
- - Click on the current file
101
- - Click publish changes
97
+ * Click on the Figma logo at the top left
98
+ * Click on `Libraries`
99
+ * Click on the current file
100
+ * Click publish changes
102
101
 
103
102
  You'll need a developer token if you don't have one already
104
103
 
105
- - Click on the Figma logo in the top left
106
- - Go to `Help and Account`
107
- - Click on `Account Settings`
108
- - Scroll to `Personal Access Token`
109
- - Enter a token name and hit enter
110
- - Note that token for the next steps
104
+ * Click on the Figma logo in the top left
105
+ * Go to `Help and Account`
106
+ * Click on `Account Settings`
107
+ * Scroll to `Personal Access Token`
108
+ * Enter a token name and hit enter
109
+ * Note that token for the next steps
111
110
 
112
111
  ## Create a project
113
112
 
114
- - Run `npm install -g handoff-app`
115
- - Create a new directory for your project `mkdir my-new-project`
116
- - In that folder (`cd my-new-project`), run `handoff-app fetch`
117
- - Handoff will prompt for your developer token, and the file id of your figma
118
- file. The file id can be found in the url of the file -
119
- https://www.figma.com/file/{file_id}/{slug}
120
- - Start the project `handoff-app start`
113
+ The easiest way to get started is using the `handoff-app init` command to scaffold a new project:
121
114
 
122
- This will fetch the latest from your Figma file and it will boot a demo site at
123
- http://localhost:3000
115
+ ```bash
116
+ npm install -g handoff-app
117
+ handoff-app init
118
+ ```
119
+
120
+ This interactive CLI will guide you through:
121
+
122
+ 1. **Project name** - Enter a name for your project directory
123
+ 2. **Project type** - Choose between:
124
+ * **Project with sample components** - Includes example components to help you get started
125
+ * **Blank project** - Only the essential configuration files
126
+ 3. **Figma configuration** - Optionally provide your Figma project ID and developer access token
127
+
128
+ The scaffolding will:
129
+
130
+ * Create a new directory with your project name
131
+ * Generate all necessary configuration files
132
+ * Install dependencies automatically
133
+
134
+ ### After scaffolding
135
+
136
+ ```bash
137
+ cd my-handoff-project
138
+ npm run fetch # Fetch design tokens from Figma
139
+ npm run start # Start the documentation site
140
+ ```
141
+
142
+ This will fetch the latest from your Figma file and boot a demo site at http://localhost:3000
143
+
144
+ ### Alternative: Global installation
145
+
146
+ You can also install handoff-app globally:
147
+
148
+ ```bash
149
+ npm install -g handoff-app
150
+ create-handoff-app
151
+ ```
152
+
153
+ Or manually set up a project:
154
+
155
+ ```bash
156
+ mkdir my-new-project && cd my-new-project
157
+ handoff-app fetch # Will prompt for Figma credentials
158
+ handoff-app start
159
+ ```
124
160
 
125
161
  ## Test Fetch
126
162
 
127
- - Now go back to your Figma file and change a button color
128
- - Republish the changes to the library. Click on the publish button from the main
163
+ * Now go back to your Figma file and change a button color
164
+ * Republish the changes to the library. Click on the publish button from the main
129
165
  dropdown. You'll see a list of changes that have been made. Clicking publish
130
166
  will make those changes available to handoff.
131
- - Back in your project, open a new terminal tab and type `npm run fetch`
167
+ * Back in your project, open a new terminal tab and type `npm run fetch`
132
168
 
133
169
  Once that runs, your browser should update with the new colors.
134
170
 
135
171
  ## Further Reading
136
172
 
137
- - [Configure your project](https://www.handoff.com/docs/customization)
138
- - [Customize the content](https://www.handoff.com/docs/customization/content)
139
- - [Integrate tokens with your project](https://www.handoff.com/docs/tokens/integration)
140
- - [Build to Static Assets](https://www.handoff.com/docs/tokens/publishing)
141
- - [Integrate with Github Actions CI/CD](https://www.handoff.com/docs/infrastructure/github/)
142
- - [Integrate with Bitbucket Pipelines CI/CD](https://www.handoff.com/docs/infrastructure/bitbucket/)
173
+ * [Configure your project](https://www.handoff.com/docs/customization)
174
+ * [Customize the content](https://www.handoff.com/docs/customization/content)
175
+ * [Integrate tokens with your project](https://www.handoff.com/docs/tokens/integration)
176
+ * [Build to Static Assets](https://www.handoff.com/docs/tokens/publishing)
177
+ * [Integrate with Github Actions CI/CD](https://www.handoff.com/docs/infrastructure/github/)
178
+ * [Integrate with Bitbucket Pipelines CI/CD](https://www.handoff.com/docs/infrastructure/bitbucket/)
143
179
 
144
180
  ## Maintainers
145
181
 
package/dist/cli.js CHANGED
@@ -19,7 +19,7 @@ class HandoffCliError extends Error {
19
19
  * Show the help message
20
20
  */
21
21
  const showVersion = () => {
22
- return 'Handoff App - 1.0.3';
22
+ return 'Handoff App - 1.0.6';
23
23
  };
24
24
  /**
25
25
  * Define a CLI error
@@ -1 +1 @@
1
- export declare const commands: import("yargs").CommandModule<{}, import("./dev").DevArgs>[];
1
+ export declare const commands: import("yargs").CommandModule<{}, import("./init").InitArgs>[];
@@ -11,6 +11,7 @@ const config_1 = __importDefault(require("./eject/config"));
11
11
  const pages_1 = __importDefault(require("./eject/pages"));
12
12
  const theme_1 = __importDefault(require("./eject/theme"));
13
13
  const fetch_1 = __importDefault(require("./fetch"));
14
+ const init_1 = __importDefault(require("./init"));
14
15
  const component_1 = __importDefault(require("./make/component"));
15
16
  const page_1 = __importDefault(require("./make/page"));
16
17
  const template_1 = __importDefault(require("./make/template"));
@@ -25,6 +26,7 @@ exports.commands = [
25
26
  pages_1.default,
26
27
  theme_1.default,
27
28
  fetch_1.default,
29
+ init_1.default,
28
30
  page_1.default,
29
31
  component_1.default,
30
32
  template_1.default,
@@ -0,0 +1,5 @@
1
+ import { CommandModule } from 'yargs';
2
+ export interface InitArgs {
3
+ }
4
+ declare const command: CommandModule<{}, InitArgs>;
5
+ export default command;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const create_1 = __importDefault(require("../../create"));
16
+ const command = {
17
+ command: 'init',
18
+ describe: 'Initialize a new Handoff project with interactive wizard',
19
+ builder: (yargs) => {
20
+ return yargs;
21
+ },
22
+ handler: () => __awaiter(void 0, void 0, void 0, function* () {
23
+ yield (0, create_1.default)();
24
+ }),
25
+ };
26
+ exports.default = command;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Main create function
3
+ */
4
+ declare const create: () => Promise<void>;
5
+ export default create;
package/dist/create.js ADDED
@@ -0,0 +1,293 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
+ return new (P || (P = Promise))(function (resolve, reject) {
28
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
32
+ });
33
+ };
34
+ var __importDefault = (this && this.__importDefault) || function (mod) {
35
+ return (mod && mod.__esModule) ? mod : { "default": mod };
36
+ };
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ const p = __importStar(require("@clack/prompts"));
39
+ const chalk_1 = __importDefault(require("chalk"));
40
+ const child_process_1 = require("child_process");
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ /**
44
+ * Get the current handoff-app version from package.json
45
+ */
46
+ const getVersion = () => {
47
+ try {
48
+ const packageJsonPath = path.resolve(__dirname, '../package.json');
49
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
50
+ return packageJson.version || 'unknown';
51
+ }
52
+ catch (_a) {
53
+ return 'unknown';
54
+ }
55
+ };
56
+ class CreateError extends Error {
57
+ constructor(message) {
58
+ super(message);
59
+ this.name = 'CreateError';
60
+ }
61
+ }
62
+ /**
63
+ * Check if directory exists
64
+ */
65
+ const directoryExists = (dirPath) => {
66
+ try {
67
+ return fs.existsSync(dirPath);
68
+ }
69
+ catch (_a) {
70
+ return false;
71
+ }
72
+ };
73
+ /**
74
+ * Create directory recursively
75
+ */
76
+ const createDirectory = (dirPath) => {
77
+ try {
78
+ fs.mkdirSync(dirPath, { recursive: true });
79
+ }
80
+ catch (error) {
81
+ throw new CreateError(`Failed to create directory: ${error}`);
82
+ }
83
+ };
84
+ /**
85
+ * Get mandatory templates based on whether examples are included
86
+ * Uses different handoff config templates for blank vs. with-examples projects
87
+ */
88
+ const getMandatoryTemplates = (includeExamples) => [
89
+ { source: 'package.json.tpl', destination: 'package.json' },
90
+ {
91
+ source: includeExamples ? 'handoff.config.with-examples.js.tpl' : 'handoff.config.js.tpl',
92
+ destination: 'handoff.config.js'
93
+ },
94
+ { source: 'tsconfig.json.tpl', destination: 'tsconfig.json' },
95
+ { source: 'gitignore.tpl', destination: '.gitignore' },
96
+ { source: 'env.tpl', destination: '.env' },
97
+ { source: 'README.md.tpl', destination: 'README.md' },
98
+ ];
99
+ // Example component files (only included when user opts for examples)
100
+ const exampleTemplates = [
101
+ { source: 'components/button/Button.tsx.tpl', destination: 'components/button/Button.tsx', isExample: true },
102
+ { source: 'components/button/button.js.tpl', destination: 'components/button/button.js', isExample: true },
103
+ ];
104
+ /**
105
+ * Render a template string by replacing placeholders with values
106
+ */
107
+ const renderTemplate = (content, data) => {
108
+ return content.replace(/\{\{(\w+)\}\}/g, (_, key) => {
109
+ if (!(key in data)) {
110
+ console.warn(chalk_1.default.yellow(`Warning: Unknown template placeholder '{{${key}}}'`));
111
+ }
112
+ return data[key] || '';
113
+ });
114
+ };
115
+ /**
116
+ * Execute npm install
117
+ */
118
+ const runNpmInstall = (projectPath) => __awaiter(void 0, void 0, void 0, function* () {
119
+ const spinner = p.spinner();
120
+ spinner.start('Installing dependencies...');
121
+ return new Promise((resolve, reject) => {
122
+ var _a;
123
+ const npmProcess = (0, child_process_1.spawn)('npm', ['install'], {
124
+ cwd: projectPath,
125
+ stdio: 'pipe',
126
+ shell: true,
127
+ });
128
+ let stderr = '';
129
+ (_a = npmProcess.stderr) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
130
+ stderr += data.toString();
131
+ });
132
+ npmProcess.on('close', (code) => {
133
+ if (code === 0) {
134
+ spinner.stop('Dependencies installed successfully');
135
+ resolve();
136
+ }
137
+ else {
138
+ spinner.stop('Failed to install dependencies');
139
+ reject(new CreateError(`npm install failed: ${stderr}`));
140
+ }
141
+ });
142
+ npmProcess.on('error', (error) => {
143
+ spinner.stop('Failed to install dependencies');
144
+ reject(new CreateError(`Failed to run npm install: ${error.message}`));
145
+ });
146
+ });
147
+ });
148
+ /**
149
+ * Validate project name
150
+ */
151
+ const validateProjectName = (name) => {
152
+ const validNameRegex = /^[a-zA-Z0-9-_]+$/;
153
+ return validNameRegex.test(name) && name.length > 0;
154
+ };
155
+ /**
156
+ * Main create function
157
+ */
158
+ const create = () => __awaiter(void 0, void 0, void 0, function* () {
159
+ const version = getVersion();
160
+ p.intro(chalk_1.default.bgCyan.black(` Handoff App Creator (v${version}) `));
161
+ try {
162
+ // Get project name
163
+ const projectName = yield p.text({
164
+ message: 'Enter project name:',
165
+ placeholder: 'my-handoff-project',
166
+ validate: (value) => {
167
+ if (!value.trim())
168
+ return 'Project name is required';
169
+ if (!validateProjectName(value)) {
170
+ return 'Invalid project name. Use only letters, numbers, hyphens, and underscores.';
171
+ }
172
+ },
173
+ });
174
+ if (p.isCancel(projectName)) {
175
+ p.cancel('Project creation cancelled.');
176
+ process.exit(0);
177
+ }
178
+ const projectPath = path.resolve(process.cwd(), projectName);
179
+ // Check if directory exists
180
+ if (directoryExists(projectPath)) {
181
+ const overwrite = yield p.confirm({
182
+ message: `Directory "${projectName}" already exists. Do you want to overwrite it?`,
183
+ initialValue: false,
184
+ });
185
+ if (p.isCancel(overwrite) || !overwrite) {
186
+ p.cancel('Project creation cancelled.');
187
+ process.exit(0);
188
+ }
189
+ // Remove existing directory
190
+ p.log.info('Removing existing directory...');
191
+ fs.rmSync(projectPath, { recursive: true, force: true });
192
+ }
193
+ // Create project directory
194
+ p.log.info('Creating project directory...');
195
+ createDirectory(projectPath);
196
+ // Ask if user wants a blank project or one with examples
197
+ p.log.step(chalk_1.default.blue('Project Configuration'));
198
+ const projectType = yield p.select({
199
+ message: 'What type of project would you like to create?',
200
+ options: [
201
+ { value: 'with-examples', label: 'Project with sample components', hint: 'Includes example components to get started' },
202
+ { value: 'blank', label: 'Blank project', hint: 'Only mandatory files, no examples' },
203
+ ],
204
+ initialValue: 'with-examples',
205
+ });
206
+ if (p.isCancel(projectType)) {
207
+ p.cancel('Project creation cancelled.');
208
+ process.exit(0);
209
+ }
210
+ const includeExamples = projectType === 'with-examples';
211
+ // Get Figma project ID
212
+ p.log.step(chalk_1.default.blue('Figma Configuration'));
213
+ const figmaProjectId = yield p.text({
214
+ message: 'Enter Figma project ID:',
215
+ placeholder: 'Optional - can be configured later',
216
+ });
217
+ if (p.isCancel(figmaProjectId)) {
218
+ p.cancel('Project creation cancelled.');
219
+ process.exit(0);
220
+ }
221
+ // Get Figma access token
222
+ let figmaAccessToken = '';
223
+ if (figmaProjectId) {
224
+ figmaAccessToken = yield p.password({
225
+ message: 'Enter Figma developer access token:',
226
+ });
227
+ if (p.isCancel(figmaAccessToken)) {
228
+ p.cancel('Project creation cancelled.');
229
+ process.exit(0);
230
+ }
231
+ }
232
+ const options = {
233
+ projectName: projectName,
234
+ figmaProjectId: (figmaProjectId || '').trim(),
235
+ figmaAccessToken: (figmaAccessToken || '').trim(),
236
+ };
237
+ // Create files
238
+ const spinner = p.spinner();
239
+ spinner.start('Creating project files...');
240
+ const templateDir = path.resolve(__dirname, '../templates/default');
241
+ if (!fs.existsSync(templateDir)) {
242
+ throw new CreateError(`Template directory not found at ${templateDir}. This may indicate a packaging issue.`);
243
+ }
244
+ // Combine mandatory templates with example templates based on user choice
245
+ const templates = [
246
+ ...getMandatoryTemplates(includeExamples),
247
+ ...(includeExamples ? exampleTemplates : []),
248
+ ];
249
+ try {
250
+ for (const template of templates) {
251
+ const templatePath = path.join(templateDir, template.source);
252
+ const destinationPath = path.join(projectPath, template.destination);
253
+ try {
254
+ const content = fs.readFileSync(templatePath, 'utf8');
255
+ const rendered = renderTemplate(content, options);
256
+ fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
257
+ fs.writeFileSync(destinationPath, rendered);
258
+ }
259
+ catch (err) {
260
+ throw new CreateError(`Failed to create file ${template.destination}: ${err}`);
261
+ }
262
+ }
263
+ spinner.stop('Project files created');
264
+ }
265
+ catch (err) {
266
+ spinner.stop('Failed to create project files');
267
+ throw err;
268
+ }
269
+ // Install dependencies
270
+ yield runNpmInstall(projectPath);
271
+ // Success message
272
+ const nextSteps = [`cd ${projectName}`];
273
+ if (figmaProjectId && figmaAccessToken) {
274
+ nextSteps.push('npm run fetch');
275
+ }
276
+ nextSteps.push('npm run start');
277
+ p.note(nextSteps.join('\n'), 'Next steps');
278
+ const outroMessage = includeExamples
279
+ ? 'Project created successfully with sample components! First, fetch your Figma data (optional), then start the documentation site.'
280
+ : 'Blank project created successfully! First, fetch your Figma data, then start the documentation site.';
281
+ p.outro(chalk_1.default.green(outroMessage));
282
+ }
283
+ catch (error) {
284
+ if (error instanceof CreateError) {
285
+ p.cancel(chalk_1.default.red(`Error: ${error.message}`));
286
+ }
287
+ else {
288
+ p.cancel(chalk_1.default.red(`Unexpected error: ${error}`));
289
+ }
290
+ process.exit(1);
291
+ }
292
+ });
293
+ exports.default = create;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "handoff-app",
3
- "version": "1.0.4",
3
+ "version": "1.0.6-canary.0",
4
4
  "description": "Automated documentation toolchain for building client side documentation from figma",
5
5
  "author ": {
6
6
  "name": "Convertiv",
@@ -309,7 +309,7 @@ const staticBuildComponentMenu = (type?: boolean | string) => {
309
309
  if (component.name) {
310
310
  title = component.name;
311
311
  }
312
- menuGroup.menu.push({ path: `${basePath}/system/component/${component.id}`, title });
312
+ menuGroup.menu.push({ path: `${basePath}system/component/${component.id}`, title });
313
313
  });
314
314
  // sort the menu group by name alphabetical
315
315
  menuGroup.menu = menuGroup.menu.sort((a, b) => a.title.localeCompare(b.title));
@@ -326,19 +326,19 @@ const staticBuildTokensMenu = () => {
326
326
  const menu = [
327
327
  {
328
328
  title: `Foundations`,
329
- path: `${basePath}/system/tokens/foundations`,
329
+ path: `${basePath}system/tokens/foundations`,
330
330
  menu: [
331
331
  {
332
332
  title: `Colors`,
333
- path: `${basePath}/system/tokens/foundations/colors`,
333
+ path: `${basePath}system/tokens/foundations/colors`,
334
334
  },
335
335
  {
336
336
  title: `Effects`,
337
- path: `${basePath}/system/tokens/foundations/effects`,
337
+ path: `${basePath}system/tokens/foundations/effects`,
338
338
  },
339
339
  {
340
340
  title: `Typography`,
341
- path: `${basePath}/system/tokens/foundations/typography`,
341
+ path: `${basePath}system/tokens/foundations/typography`,
342
342
  },
343
343
  ],
344
344
  },
@@ -358,14 +358,14 @@ const staticBuildTokensMenu = () => {
358
358
  if (component.name) {
359
359
  title = component.name;
360
360
  }
361
- componentMenuItems.push({ path: `${basePath}/system/tokens/components/${component.id}`, title });
361
+ componentMenuItems.push({ path: `${basePath}system/tokens/components/${component.id}`, title });
362
362
  });
363
363
  });
364
364
 
365
365
  if (componentMenuItems.length > 0) {
366
366
  menu.push({
367
367
  title: `Components`,
368
- path: `${basePath}/system/tokens/components`,
368
+ path: `${basePath}system/tokens/components`,
369
369
  menu: componentMenuItems,
370
370
  });
371
371
  }
@@ -378,7 +378,7 @@ const staticBuildTokenMenu = () => {
378
378
 
379
379
  let subSections = {
380
380
  title: 'Tokens',
381
- path: `${basePath}/system/tokens`,
381
+ path: `${basePath}system/tokens`,
382
382
  menu: [],
383
383
  };
384
384
  const tokens = getTokens();