handoff-app 1.0.3 → 1.0.5

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.
Files changed (55) hide show
  1. package/Changelog.md +16 -5
  2. package/README.md +93 -57
  3. package/dist/cli.js +1 -1
  4. package/dist/commands/index.d.ts +1 -1
  5. package/dist/commands/index.js +2 -0
  6. package/dist/commands/init/index.d.ts +5 -0
  7. package/dist/commands/init/index.js +26 -0
  8. package/dist/create.d.ts +5 -0
  9. package/dist/create.js +293 -0
  10. package/dist/index.js +4 -1
  11. package/dist/transformers/plugins/ssr-render.js +4 -3
  12. package/dist/transformers/preview/component/api.js +3 -2
  13. package/dist/transformers/preview/component/builder.js +2 -1
  14. package/dist/transformers/utils/handlebars.js +3 -2
  15. package/package.json +1 -1
  16. package/src/app/components/Component/Preview.tsx +121 -121
  17. package/src/app/components/ComponentSearch.tsx +1 -1
  18. package/src/app/components/util/index.ts +17 -9
  19. package/src/app/pages/system/component/[component]/index.tsx +1 -1
  20. package/src/app/pages/system/index.tsx +2 -2
  21. package/src/app/pages/system/tokens/index.tsx +10 -10
  22. package/src/cli.ts +1 -1
  23. package/src/commands/index.ts +2 -0
  24. package/src/commands/init/index.ts +17 -0
  25. package/src/create.ts +302 -0
  26. package/src/index.ts +3 -0
  27. package/src/transformers/plugins/ssr-render.ts +3 -3
  28. package/src/transformers/preview/component/api.ts +1 -1
  29. package/src/transformers/preview/component/builder.ts +1 -1
  30. package/src/transformers/utils/handlebars.ts +2 -2
  31. package/templates/default/README.md.tpl +15 -0
  32. package/templates/default/components/button/Button.tsx.tpl +92 -0
  33. package/templates/default/components/button/button.js.tpl +62 -0
  34. package/templates/default/env.tpl +2 -0
  35. package/templates/default/gitignore.tpl +7 -0
  36. package/templates/default/handoff.config.js.tpl +181 -0
  37. package/templates/default/handoff.config.with-examples.js.tpl +182 -0
  38. package/templates/default/package.json.tpl +20 -0
  39. package/templates/default/tsconfig.json.tpl +24 -0
  40. package/dist/commands/platform/init.d.ts +0 -6
  41. package/dist/commands/platform/init.js +0 -231
  42. package/dist/commands/platform/login.d.ts +0 -6
  43. package/dist/commands/platform/login.js +0 -108
  44. package/dist/commands/platform/logout.d.ts +0 -6
  45. package/dist/commands/platform/logout.js +0 -88
  46. package/dist/commands/platform/pull.d.ts +0 -8
  47. package/dist/commands/platform/pull.js +0 -112
  48. package/dist/commands/platform/push.d.ts +0 -8
  49. package/dist/commands/platform/push.js +0 -428
  50. package/dist/platform/client.d.ts +0 -122
  51. package/dist/platform/client.js +0 -228
  52. package/dist/platform/credentials.d.ts +0 -40
  53. package/dist/platform/credentials.js +0 -91
  54. package/dist/platform/sync.d.ts +0 -68
  55. package/dist/platform/sync.js +0 -349
@@ -0,0 +1,181 @@
1
+
2
+
3
+ /** @type {import('handoff-app').Config} */
4
+ module.exports = {
5
+ app: {
6
+ theme: "default",
7
+ title: "{{projectName}} Design System",
8
+ client: "{{projectName}}",
9
+ google_tag_manager: null,
10
+ attribution: true,
11
+ type_copy: "Almost before we knew it, we had left the ground.",
12
+ type_sort: [
13
+ "Heading 1",
14
+ "Heading 2",
15
+ "Heading 3",
16
+ "Heading 4",
17
+ "Heading 5",
18
+ "Heading 6",
19
+ "Paragraph",
20
+ "Subheading",
21
+ "Blockquote",
22
+ "Input Labels",
23
+ "Link"
24
+ ],
25
+ color_sort: [
26
+ "primary",
27
+ "secondary",
28
+ "extra",
29
+ "system"
30
+ ],
31
+ component_sort: [
32
+ "primary",
33
+ "secondary",
34
+ "transparent"
35
+ ],
36
+ base_path: "",
37
+ breakpoints: {
38
+ sm: { size: 576, name: "Small" },
39
+ md: { size: 768, name: "Medium" },
40
+ lg: { size: 992, name: "Large" },
41
+ xl: { size: 1200, name: "Extra Large" }
42
+ }
43
+ },
44
+
45
+ // entries: {
46
+ // /**
47
+ // * Array of component paths to be included in the build
48
+ // * Add your component directories here
49
+ // */
50
+ // components: ["components/button"]
51
+ // },
52
+
53
+ hooks: {
54
+ /**
55
+ * Optional validation callback for components
56
+ * @param component - The component instance to validate
57
+ * @returns A record of validation results where keys are validation types and values are detailed validation results
58
+ *
59
+ * @example
60
+ * validateComponent: async (component) => ({
61
+ * a11y: {
62
+ * description: 'Accessibility validation check',
63
+ * passed: true,
64
+ * messages: ['No accessibility issues found']
65
+ * },
66
+ * responsive: {
67
+ * description: 'Responsive design validation',
68
+ * passed: false,
69
+ * messages: ['Component breaks at mobile breakpoint']
70
+ * }
71
+ * })
72
+ */
73
+ // validateComponent: async (component) => {
74
+ // // Add your custom validation logic here
75
+ // return {};
76
+ // },
77
+
78
+ /**
79
+ * Optional hook to override the SSR build configuration used in the ssrRenderPlugin
80
+ * @param config - The default esbuild configuration
81
+ * @returns Modified esbuild configuration
82
+ *
83
+ * @example
84
+ * ssrBuildConfig: (config) => {
85
+ * // Modify the esbuild config as needed
86
+ * return config;
87
+ * }
88
+ */
89
+ // ssrBuildConfig: (config) => {
90
+ // // Add your custom SSR build configuration here
91
+ // return config;
92
+ // },
93
+
94
+ /**
95
+ * Optional hook to override the client-side build configuration used in the ssrRenderPlugin
96
+ * @param config - The default esbuild configuration
97
+ * @returns Modified esbuild configuration
98
+ *
99
+ * @example
100
+ * clientBuildConfig: (config) => {
101
+ * // Modify the esbuild config as needed
102
+ * return config;
103
+ * }
104
+ */
105
+ // clientBuildConfig: (config) => {
106
+ // // Add your custom client build configuration here
107
+ // return config;
108
+ // },
109
+
110
+ /**
111
+ * Optional hook to specify which export property contains the schema
112
+ * @param exports - The module exports object containing the schema
113
+ * @returns The schema object from the exports
114
+ *
115
+ * @example
116
+ * getSchemaFromExports: (exports) => exports.customSchema || exports.default
117
+ */
118
+ // getSchemaFromExports: (exports) => {
119
+ // // Add your custom schema extraction logic here
120
+ // return exports.default;
121
+ // },
122
+
123
+ /**
124
+ * Optional hook to transform the schema into properties
125
+ * @param schema - The schema object to transform
126
+ * @returns The transformed properties object
127
+ */
128
+ // schemaToProperties: (schema) => {
129
+ // // Add your custom schema to properties transformation here
130
+ // return {};
131
+ // },
132
+
133
+ /**
134
+ * Optional hook to override the JavaScript Vite configuration
135
+ * @param config - The default Vite configuration
136
+ * @returns Modified Vite configuration
137
+ *
138
+ * @example
139
+ * jsBuildConfig: (config) => {
140
+ * // Modify the Vite config as needed
141
+ * return config;
142
+ * }
143
+ */
144
+ // jsBuildConfig: (config) => {
145
+ // // Add your custom JavaScript build configuration here
146
+ // return config;
147
+ // },
148
+
149
+ /**
150
+ * Optional hook to override the CSS Vite configuration
151
+ * @param config - The default Vite configuration
152
+ * @returns Modified Vite configuration
153
+ *
154
+ * @example
155
+ * cssBuildConfig: (config) => {
156
+ * // Modify the Vite config as needed
157
+ * return config;
158
+ * }
159
+ */
160
+ // cssBuildConfig: (config) => {
161
+ // // Add your custom CSS build configuration here
162
+ // return config;
163
+ // },
164
+
165
+ /**
166
+ * Optional hook to override the HTML Vite configuration
167
+ * @param config - The default Vite configuration
168
+ * @returns Modified Vite configuration
169
+ *
170
+ * @example
171
+ * htmlBuildConfig: (config) => {
172
+ * // Modify the Vite config as needed
173
+ * return config;
174
+ * }
175
+ */
176
+ // htmlBuildConfig: (config) => {
177
+ // // Add your custom HTML build configuration here
178
+ // return config;
179
+ // },
180
+ },
181
+ };
@@ -0,0 +1,182 @@
1
+
2
+
3
+
4
+ /** @type {import('handoff-app').Config} */
5
+ module.exports = {
6
+ app: {
7
+ theme: "default",
8
+ title: "{{projectName}} Design System",
9
+ client: "{{projectName}}",
10
+ google_tag_manager: null,
11
+ attribution: true,
12
+ type_copy: "Almost before we knew it, we had left the ground.",
13
+ type_sort: [
14
+ "Heading 1",
15
+ "Heading 2",
16
+ "Heading 3",
17
+ "Heading 4",
18
+ "Heading 5",
19
+ "Heading 6",
20
+ "Paragraph",
21
+ "Subheading",
22
+ "Blockquote",
23
+ "Input Labels",
24
+ "Link"
25
+ ],
26
+ color_sort: [
27
+ "primary",
28
+ "secondary",
29
+ "extra",
30
+ "system"
31
+ ],
32
+ component_sort: [
33
+ "primary",
34
+ "secondary",
35
+ "transparent"
36
+ ],
37
+ base_path: "",
38
+ breakpoints: {
39
+ sm: { size: 576, name: "Small" },
40
+ md: { size: 768, name: "Medium" },
41
+ lg: { size: 992, name: "Large" },
42
+ xl: { size: 1200, name: "Extra Large" }
43
+ }
44
+ },
45
+
46
+ entries: {
47
+ /**
48
+ * Array of component paths to be included in the build
49
+ * Add your component directories here
50
+ */
51
+ components: ["components/button"]
52
+ },
53
+
54
+ hooks: {
55
+ /**
56
+ * Optional validation callback for components
57
+ * @param component - The component instance to validate
58
+ * @returns A record of validation results where keys are validation types and values are detailed validation results
59
+ *
60
+ * @example
61
+ * validateComponent: async (component) => ({
62
+ * a11y: {
63
+ * description: 'Accessibility validation check',
64
+ * passed: true,
65
+ * messages: ['No accessibility issues found']
66
+ * },
67
+ * responsive: {
68
+ * description: 'Responsive design validation',
69
+ * passed: false,
70
+ * messages: ['Component breaks at mobile breakpoint']
71
+ * }
72
+ * })
73
+ */
74
+ // validateComponent: async (component) => {
75
+ // // Add your custom validation logic here
76
+ // return {};
77
+ // },
78
+
79
+ /**
80
+ * Optional hook to override the SSR build configuration used in the ssrRenderPlugin
81
+ * @param config - The default esbuild configuration
82
+ * @returns Modified esbuild configuration
83
+ *
84
+ * @example
85
+ * ssrBuildConfig: (config) => {
86
+ * // Modify the esbuild config as needed
87
+ * return config;
88
+ * }
89
+ */
90
+ // ssrBuildConfig: (config) => {
91
+ // // Add your custom SSR build configuration here
92
+ // return config;
93
+ // },
94
+
95
+ /**
96
+ * Optional hook to override the client-side build configuration used in the ssrRenderPlugin
97
+ * @param config - The default esbuild configuration
98
+ * @returns Modified esbuild configuration
99
+ *
100
+ * @example
101
+ * clientBuildConfig: (config) => {
102
+ * // Modify the esbuild config as needed
103
+ * return config;
104
+ * }
105
+ */
106
+ // clientBuildConfig: (config) => {
107
+ // // Add your custom client build configuration here
108
+ // return config;
109
+ // },
110
+
111
+ /**
112
+ * Optional hook to specify which export property contains the schema
113
+ * @param exports - The module exports object containing the schema
114
+ * @returns The schema object from the exports
115
+ *
116
+ * @example
117
+ * getSchemaFromExports: (exports) => exports.customSchema || exports.default
118
+ */
119
+ // getSchemaFromExports: (exports) => {
120
+ // // Add your custom schema extraction logic here
121
+ // return exports.default;
122
+ // },
123
+
124
+ /**
125
+ * Optional hook to transform the schema into properties
126
+ * @param schema - The schema object to transform
127
+ * @returns The transformed properties object
128
+ */
129
+ // schemaToProperties: (schema) => {
130
+ // // Add your custom schema to properties transformation here
131
+ // return {};
132
+ // },
133
+
134
+ /**
135
+ * Optional hook to override the JavaScript Vite configuration
136
+ * @param config - The default Vite configuration
137
+ * @returns Modified Vite configuration
138
+ *
139
+ * @example
140
+ * jsBuildConfig: (config) => {
141
+ * // Modify the Vite config as needed
142
+ * return config;
143
+ * }
144
+ */
145
+ // jsBuildConfig: (config) => {
146
+ // // Add your custom JavaScript build configuration here
147
+ // return config;
148
+ // },
149
+
150
+ /**
151
+ * Optional hook to override the CSS Vite configuration
152
+ * @param config - The default Vite configuration
153
+ * @returns Modified Vite configuration
154
+ *
155
+ * @example
156
+ * cssBuildConfig: (config) => {
157
+ * // Modify the Vite config as needed
158
+ * return config;
159
+ * }
160
+ */
161
+ // cssBuildConfig: (config) => {
162
+ // // Add your custom CSS build configuration here
163
+ // return config;
164
+ // },
165
+
166
+ /**
167
+ * Optional hook to override the HTML Vite configuration
168
+ * @param config - The default Vite configuration
169
+ * @returns Modified Vite configuration
170
+ *
171
+ * @example
172
+ * htmlBuildConfig: (config) => {
173
+ * // Modify the Vite config as needed
174
+ * return config;
175
+ * }
176
+ */
177
+ // htmlBuildConfig: (config) => {
178
+ // // Add your custom HTML build configuration here
179
+ // return config;
180
+ // },
181
+ },
182
+ };
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "{{projectName}}",
3
+ "version": "0.1.0",
4
+ "description": "A handoff app project",
5
+ "private": true,
6
+ "scripts": {
7
+ "start": "handoff-app start",
8
+ "dev": "handoff-app dev",
9
+ "build": "handoff-app build:app",
10
+ "fetch": "handoff-app fetch"
11
+ },
12
+ "dependencies": {
13
+ "handoff-app": "^1.0.1"
14
+ },
15
+ "devDependencies": {},
16
+ "engines": {
17
+ "node": ">=16.0.0",
18
+ "npm": ">=8.0.0"
19
+ }
20
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
5
+ "jsx": "react-jsx",
6
+ "module": "ESNext",
7
+ "moduleResolution": "bundler",
8
+ "resolveJsonModule": true,
9
+ "allowJs": true,
10
+ "strict": true,
11
+ "esModuleInterop": true,
12
+ "skipLibCheck": true,
13
+ "forceConsistentCasingInFileNames": true,
14
+ "allowSyntheticDefaultImports": true,
15
+ "isolatedModules": true,
16
+ "noEmit": true
17
+ },
18
+ "include": [
19
+ "components/**/*"
20
+ ],
21
+ "exclude": [
22
+ "node_modules"
23
+ ]
24
+ }
@@ -1,6 +0,0 @@
1
- import { CommandModule } from 'yargs';
2
- export interface PlatformInitArgs {
3
- url?: string;
4
- }
5
- declare const command: CommandModule<{}, PlatformInitArgs>;
6
- export default command;
@@ -1,231 +0,0 @@
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 fs_extra_1 = __importDefault(require("fs-extra"));
41
- const path_1 = __importDefault(require("path"));
42
- const client_1 = require("../../platform/client");
43
- const credentials_1 = require("../../platform/credentials");
44
- const DEFAULT_PLATFORM_URL = 'http://localhost:3000';
45
- const CONFIG_FILES = ['handoff.config.json', 'handoff.config.js', 'handoff.config.cjs'];
46
- const command = {
47
- command: 'platform:init',
48
- describe: 'Link this directory to a Handoff platform project',
49
- builder: (yargs) => {
50
- return yargs.option('url', {
51
- type: 'string',
52
- description: 'Platform URL',
53
- default: process.env.HANDOFF_PLATFORM_URL || DEFAULT_PLATFORM_URL,
54
- });
55
- },
56
- handler: (args) => __awaiter(void 0, void 0, void 0, function* () {
57
- const baseUrl = (args.url || DEFAULT_PLATFORM_URL).replace(/\/+$/, '');
58
- const workingDir = process.cwd();
59
- p.intro(chalk_1.default.bold('Handoff Platform Init'));
60
- // 1. Check login
61
- const token = (0, credentials_1.getToken)(baseUrl);
62
- if (!token) {
63
- p.cancel(`Not logged in to ${baseUrl}. Run ${chalk_1.default.cyan('handoff-app platform:login')} first.`);
64
- process.exit(1);
65
- }
66
- const spinner = p.spinner();
67
- try {
68
- // 2. Fetch project list
69
- spinner.start('Fetching projects...');
70
- const projects = yield (0, client_1.listProjects)(baseUrl, token);
71
- spinner.stop('Projects loaded.');
72
- if (projects.length === 0) {
73
- p.cancel('No projects found. Create a project in the Handoff platform first.');
74
- process.exit(1);
75
- }
76
- // 3. Interactive project selection
77
- const projectId = yield p.select({
78
- message: 'Select a project to link:',
79
- options: projects.map((proj) => ({
80
- value: proj.id,
81
- label: `${proj.name} (${proj.orgName})`,
82
- hint: `${proj.slug} — ${proj.role}`,
83
- })),
84
- });
85
- if (p.isCancel(projectId)) {
86
- p.cancel('Init cancelled.');
87
- process.exit(0);
88
- }
89
- const selectedProject = projects.find((proj) => proj.id === projectId);
90
- // 4. Check if a local config exists
91
- const existingConfigFile = CONFIG_FILES.find((file) => fs_extra_1.default.existsSync(path_1.default.resolve(workingDir, file)));
92
- if (!existingConfigFile) {
93
- // No local config — pull it from the remote project
94
- spinner.start('No local config found. Fetching config from platform...');
95
- try {
96
- const manifest = yield (0, client_1.getManifest)(baseUrl, token, selectedProject.id);
97
- // Look for handoff.config.js in the manifest
98
- const remoteConfigKey = Object.keys(manifest.files).find((key) => key === 'handoff.config.js' || key === 'handoff.config.json' || key === 'handoff.config.cjs');
99
- if (remoteConfigKey) {
100
- const content = yield (0, client_1.downloadFile)(baseUrl, token, selectedProject.id, remoteConfigKey);
101
- const localConfigPath = path_1.default.resolve(workingDir, remoteConfigKey);
102
- fs_extra_1.default.writeFileSync(localConfigPath, content);
103
- spinner.stop(`Downloaded ${remoteConfigKey} from platform.`);
104
- // Now inject the platform fields into it
105
- yield injectPlatformFields(localConfigPath, selectedProject.id, baseUrl);
106
- }
107
- else {
108
- spinner.stop('No remote config found either. Creating a new config.');
109
- // Create a minimal config with just the platform fields
110
- const configPath = path_1.default.resolve(workingDir, 'handoff.config.js');
111
- const configContent = generateMinimalConfig(selectedProject.id, baseUrl);
112
- fs_extra_1.default.writeFileSync(configPath, configContent);
113
- }
114
- }
115
- catch (err) {
116
- spinner.stop('Could not fetch remote config.');
117
- // Fall back to creating a minimal config
118
- const configPath = path_1.default.resolve(workingDir, 'handoff.config.js');
119
- const configContent = generateMinimalConfig(selectedProject.id, baseUrl);
120
- fs_extra_1.default.writeFileSync(configPath, configContent);
121
- }
122
- }
123
- else {
124
- // Local config exists — add/update platform fields
125
- const configPath = path_1.default.resolve(workingDir, existingConfigFile);
126
- yield injectPlatformFields(configPath, selectedProject.id, baseUrl);
127
- }
128
- p.note([
129
- `Project: ${chalk_1.default.bold(selectedProject.name)}`,
130
- `Organization: ${chalk_1.default.dim(selectedProject.orgName)}`,
131
- `Project ID: ${chalk_1.default.dim(selectedProject.id)}`,
132
- `Platform: ${chalk_1.default.dim(baseUrl)}`,
133
- ].join('\n'), 'Linked successfully');
134
- p.outro(`Run ${chalk_1.default.cyan('handoff-app platform:pull')} to sync files from the platform.`);
135
- }
136
- catch (err) {
137
- spinner.stop('Init failed.');
138
- p.cancel(err.message || 'Init failed');
139
- process.exit(1);
140
- }
141
- }),
142
- };
143
- /**
144
- * Inject platformProjectId and platformUrl into an existing config file.
145
- *
146
- * For JSON files, parse and re-serialize.
147
- * For JS/CJS files, use a regex approach to add/update the fields.
148
- */
149
- function injectPlatformFields(configPath, projectId, platformUrl) {
150
- return __awaiter(this, void 0, void 0, function* () {
151
- const ext = path_1.default.extname(configPath);
152
- if (ext === '.json') {
153
- const config = fs_extra_1.default.readJsonSync(configPath);
154
- config.platformProjectId = projectId;
155
- config.platformUrl = platformUrl;
156
- fs_extra_1.default.writeJsonSync(configPath, config, { spaces: 2 });
157
- }
158
- else {
159
- // JS or CJS file — read as text, inject/replace fields
160
- let content = fs_extra_1.default.readFileSync(configPath, 'utf8');
161
- content = upsertConfigField(content, 'platformProjectId', JSON.stringify(projectId));
162
- content = upsertConfigField(content, 'platformUrl', JSON.stringify(platformUrl));
163
- fs_extra_1.default.writeFileSync(configPath, content, 'utf8');
164
- }
165
- });
166
- }
167
- /**
168
- * Insert or replace a top-level field in a JS config export.
169
- * Looks for `fieldName: ...` and replaces the value, or appends
170
- * the field before the closing `};` of module.exports.
171
- */
172
- function upsertConfigField(source, fieldName, value) {
173
- // Pattern to match an existing field assignment (with optional trailing comma)
174
- const existingPattern = new RegExp(`(${fieldName}\\s*:\\s*)([^,}\\n]+)(,?)`);
175
- if (existingPattern.test(source)) {
176
- return source.replace(existingPattern, `$1${value}$3`);
177
- }
178
- // Insert before the last `};` (end of module.exports)
179
- // Handle both `module.exports = {` and `export default {` styles
180
- // Also ensure the previous line ends with a comma so the JS stays valid
181
- const closingBrace = /(\n)(};?\s*$)/;
182
- if (closingBrace.test(source)) {
183
- // Ensure the last property line before the closing brace has a trailing comma
184
- source = ensureTrailingComma(source);
185
- return source.replace(closingBrace, `$1 ${fieldName}: ${value},\n$2`);
186
- }
187
- // Fallback: just append as a comment warning
188
- return source + `\n// Added by platform:init — could not auto-inject:\n// ${fieldName}: ${value}\n`;
189
- }
190
- /**
191
- * Ensure the last property line before the closing `};` has a trailing comma.
192
- * This prevents invalid JS when appending new properties to an object literal.
193
- */
194
- function ensureTrailingComma(source) {
195
- const lines = source.split('\n');
196
- // Find the closing `};` or `}` at the start of a line (module.exports / export default)
197
- let closingIdx = -1;
198
- for (let i = lines.length - 1; i >= 0; i--) {
199
- if (/^};?\s*$/.test(lines[i])) {
200
- closingIdx = i;
201
- break;
202
- }
203
- }
204
- if (closingIdx <= 0)
205
- return source;
206
- // Walk backwards past blank lines and comment-only lines to find the last property line
207
- let lastPropIdx = closingIdx - 1;
208
- while (lastPropIdx >= 0 && (/^\s*$/.test(lines[lastPropIdx]) || /^\s*\/\//.test(lines[lastPropIdx]))) {
209
- lastPropIdx--;
210
- }
211
- if (lastPropIdx < 0)
212
- return source;
213
- // Strip inline comments for the check, but preserve them in the output
214
- const stripped = lines[lastPropIdx].replace(/\s*\/\/.*$/, '').trimEnd();
215
- if (stripped.length > 0 && !stripped.endsWith(',') && !stripped.endsWith('{')) {
216
- // Insert a comma after the last significant character, preserving any inline comment
217
- lines[lastPropIdx] = lines[lastPropIdx].replace(/(\S)(\s*(?:\/\/.*)?)$/, '$1,$2');
218
- }
219
- return lines.join('\n');
220
- }
221
- /**
222
- * Generate a minimal handoff.config.js with platform fields.
223
- */
224
- function generateMinimalConfig(projectId, platformUrl) {
225
- return `module.exports = {
226
- platformProjectId: ${JSON.stringify(projectId)},
227
- platformUrl: ${JSON.stringify(platformUrl)},
228
- };
229
- `;
230
- }
231
- exports.default = command;
@@ -1,6 +0,0 @@
1
- import { CommandModule } from 'yargs';
2
- export interface PlatformLoginArgs {
3
- url?: string;
4
- }
5
- declare const command: CommandModule<{}, PlatformLoginArgs>;
6
- export default command;