@getxflow/cli 0.2.0 → 0.3.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/dist/bin.js +0 -4
- package/dist/commands/projects.js +1 -1
- package/dist/commands/sources.js +2 -2
- package/dist/template.js +1 -1
- package/dist/tree.js +3 -0
- package/dist/version.js +1 -1
- package/dist/zip.js +20 -0
- package/package.json +1 -1
- package/skills/xflow/SKILL.md +26 -3
package/dist/bin.js
CHANGED
|
@@ -69,10 +69,6 @@ async function run(args) {
|
|
|
69
69
|
}
|
|
70
70
|
throw new errors_1.CliError(`Unknown command: mcp ${second}`, 'Available: install');
|
|
71
71
|
case 'functions':
|
|
72
|
-
// The command is gone, but silence would leave the habit unexplained.
|
|
73
|
-
if (second === 'deploy') {
|
|
74
|
-
throw new errors_1.CliError('Functions are deployed by the build now', 'Run xflow deploy: it ships the functions and then builds the application with their addresses');
|
|
75
|
-
}
|
|
76
72
|
if (second === 'invoke') {
|
|
77
73
|
await (0, functions_1.functionsInvoke)(rest);
|
|
78
74
|
return;
|
|
@@ -63,7 +63,7 @@ async function init(args) {
|
|
|
63
63
|
}
|
|
64
64
|
(0, ui_1.step)('Fetching the template');
|
|
65
65
|
const archive = await (0, api_1.apiBinary)(client, `/api/v1/templates/${templateId}/archive`);
|
|
66
|
-
const files = (0, zip_1.zipRead)(archive);
|
|
66
|
+
const files = (0, zip_1.dropDirectoryEntries)((0, zip_1.zipRead)(archive));
|
|
67
67
|
(0, ui_1.step)('Creating the project on the platform');
|
|
68
68
|
const project = await (0, api_1.apiJson)(client, '/api/v1/projects', {
|
|
69
69
|
method: 'POST',
|
package/dist/commands/sources.js
CHANGED
|
@@ -47,7 +47,7 @@ async function confirmForce(client, projectId, server, local) {
|
|
|
47
47
|
(0, ui_1.warn)(`The server holds revision ${server.revision} from ${(0, ui_1.formatAge)(server.created_at)}, ${server.file_count} files`);
|
|
48
48
|
try {
|
|
49
49
|
const info = await (0, api_1.apiJson)(client, `/api/v1/projects/${projectId}/pull`);
|
|
50
|
-
const serverPaths = new Set((0, zip_1.zipRead)(await (0, api_1.downloadUrl)(info.download_url)).map((e) => e.path));
|
|
50
|
+
const serverPaths = new Set((0, zip_1.dropDirectoryEntries)((0, zip_1.zipRead)(await (0, api_1.downloadUrl)(info.download_url))).map((e) => e.path));
|
|
51
51
|
const localPaths = new Set(local.map((f) => f.path));
|
|
52
52
|
const disappearing = [...serverPaths].filter((p) => !localPaths.has(p));
|
|
53
53
|
if (disappearing.length > 0) {
|
|
@@ -132,7 +132,7 @@ async function pull(args) {
|
|
|
132
132
|
(0, ui_1.warn)('The hash of the downloaded tree did not match the server one: the contents may have changed in transit');
|
|
133
133
|
}
|
|
134
134
|
(0, node_fs_1.mkdirSync)(target, { recursive: true });
|
|
135
|
-
for (const entry of entries) {
|
|
135
|
+
for (const entry of (0, zip_1.dropDirectoryEntries)(entries)) {
|
|
136
136
|
const path = (0, node_path_1.join)(target, entry.path);
|
|
137
137
|
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(path), { recursive: true });
|
|
138
138
|
(0, node_fs_1.writeFileSync)(path, entry.content);
|
package/dist/template.js
CHANGED
|
@@ -25,7 +25,7 @@ xflow publish # show the dev version to visitors
|
|
|
25
25
|
\`\`\`
|
|
26
26
|
|
|
27
27
|
The interface is built on the template components: \`src/components/ui\` (buttons, fields,
|
|
28
|
-
dialogs) and \`src/components/blocks\` (
|
|
28
|
+
dialogs) and \`src/components/blocks\` (a data table and charts). Layout built
|
|
29
29
|
on top of them looks like the rest of the platform, while custom colors in place of the
|
|
30
30
|
tokens from \`src/index.css\` break that consistency.
|
|
31
31
|
|
package/dist/tree.js
CHANGED
|
@@ -19,6 +19,9 @@ const DEFAULT_IGNORE = [
|
|
|
19
19
|
'.env.*',
|
|
20
20
|
'.DS_Store',
|
|
21
21
|
'Thumbs.db',
|
|
22
|
+
// Compiler cache: rewritten by every local build, so it would make a new
|
|
23
|
+
// revision out of nothing.
|
|
24
|
+
'tsconfig.tsbuildinfo',
|
|
22
25
|
];
|
|
23
26
|
const IGNORE_FILE = '.xflowignore';
|
|
24
27
|
function globToRegExpSource(pattern) {
|
package/dist/version.js
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DEFAULT_API_URL = exports.CLI_VERSION = void 0;
|
|
4
4
|
/** Keep in sync with cli/package.json. */
|
|
5
|
-
exports.CLI_VERSION = '0.
|
|
5
|
+
exports.CLI_VERSION = '0.3.0';
|
|
6
6
|
/** Overridden by XFLOW_API_URL or the `api` field in xflow.json. */
|
|
7
7
|
exports.DEFAULT_API_URL = 'https://app.getxflow.com';
|
package/dist/zip.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ZipError = void 0;
|
|
4
4
|
exports.zipCreate = zipCreate;
|
|
5
|
+
exports.dropDirectoryEntries = dropDirectoryEntries;
|
|
5
6
|
exports.zipRead = zipRead;
|
|
6
7
|
const node_zlib_1 = require("node:zlib");
|
|
7
8
|
class ZipError extends Error {
|
|
@@ -122,6 +123,25 @@ function isSafeEntryPath(path) {
|
|
|
122
123
|
}
|
|
123
124
|
return path.split('/').every((part) => part !== '' && part !== '.' && part !== '..');
|
|
124
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Drop entries whose name is a directory elsewhere in the same archive: old
|
|
128
|
+
* revisions carry folders as empty files, and unpacking them collides with the
|
|
129
|
+
* directory of the same name. Call it before writing to disk, not inside
|
|
130
|
+
* zipRead: the tree hash is checked against the archive as the server made it.
|
|
131
|
+
*/
|
|
132
|
+
function dropDirectoryEntries(entries) {
|
|
133
|
+
const directories = new Set();
|
|
134
|
+
for (const entry of entries) {
|
|
135
|
+
const segments = entry.path.split('/');
|
|
136
|
+
segments.pop();
|
|
137
|
+
let current = '';
|
|
138
|
+
for (const segment of segments) {
|
|
139
|
+
current = current ? `${current}/${segment}` : segment;
|
|
140
|
+
directories.add(current);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return entries.filter((entry) => !directories.has(entry.path));
|
|
144
|
+
}
|
|
125
145
|
function zipRead(buffer) {
|
|
126
146
|
if (buffer.length < 22)
|
|
127
147
|
throw new ZipError('The file is too small to be a zip');
|
package/package.json
CHANGED
package/skills/xflow/SKILL.md
CHANGED
|
@@ -103,6 +103,30 @@ Interface rules, checked outside `src/components/ui` and `src/components/blocks`
|
|
|
103
103
|
`react-hot-toast`, `react-dnd`) must have it mounted in `App.tsx`. Missing providers
|
|
104
104
|
build fine and give visitors a white screen.
|
|
105
105
|
|
|
106
|
+
Template integrity. The app grows out of the platform template, and part of that template
|
|
107
|
+
is not yours to change. The reference is a snapshot of the project itself, taken when the
|
|
108
|
+
platform first looked at it, so these rules never argue with work that was already there:
|
|
109
|
+
|
|
110
|
+
- Platform files must stay byte for byte as they arrived: `src/lib/theme-sync.ts`,
|
|
111
|
+
`src/lib/platform-auth.ts`, `src/contexts/platform-auth-context.tsx`,
|
|
112
|
+
`src/hooks/use-platform-auth.ts`, `src/utils/error-logger.ts`, `src/lib/xflow.ts`.
|
|
113
|
+
They wire the app to the platform, and every way they break is a silent one. Build what
|
|
114
|
+
you need around them, never inside them.
|
|
115
|
+
- The entry point keeps calling `initThemeSync()`, `initPlatformAuth()` and
|
|
116
|
+
`initErrorLogger()`, keeps importing `index.css` and keeps mounting `ThemeProvider`.
|
|
117
|
+
How the file is written is up to you.
|
|
118
|
+
- `index.html` keeps the element with `id="root"` and the script that loads `src/main`.
|
|
119
|
+
- Theme token names in `src/index.css` stay declared, in `:root` and in `.dark` alike, and
|
|
120
|
+
the Tailwind config keeps mapping them. Change the values as much as the design needs:
|
|
121
|
+
it is the names that components paint with.
|
|
122
|
+
- The Tailwind `content` globs keep covering `src/**`. Narrow them and Tailwind strips
|
|
123
|
+
every class the app uses.
|
|
124
|
+
- Files under `src/components/ui` and `src/components/blocks` may be edited freely but
|
|
125
|
+
not deleted.
|
|
126
|
+
|
|
127
|
+
A rejection names the file and the revision to take the original from:
|
|
128
|
+
`xflow pull --revision N --into ./original`, then copy the file back.
|
|
129
|
+
|
|
106
130
|
## Cloud functions
|
|
107
131
|
|
|
108
132
|
Server-side code lives in `functions/<name>/index.ts` and exports `handler`. There is no
|
|
@@ -209,8 +233,7 @@ The platform design system already ships inside the project, and the app is supp
|
|
|
209
233
|
to look like a part of the platform:
|
|
210
234
|
|
|
211
235
|
- `src/components/ui` for primitives: buttons, inputs, dialogs, tables, menus
|
|
212
|
-
- `src/components/blocks` for composed blocks: `data-table
|
|
213
|
-
`filter-bar`, `detail-panel`, `kanban`, `charts`, `user-picker`, `relation-picker`
|
|
236
|
+
- `src/components/blocks` for composed blocks: `data-table` and `charts`
|
|
214
237
|
- `src/index.css` for color tokens
|
|
215
238
|
|
|
216
239
|
Before writing your own component, check whether a block already covers it: props are
|
|
@@ -221,7 +244,7 @@ the design system sits in the project.
|
|
|
221
244
|
|
|
222
245
|
The app runs inside the platform in an iframe and receives the theme and the current
|
|
223
246
|
user from it. The `usePlatformAuth()` hook gives the name, role, permissions and the
|
|
224
|
-
list of organization members
|
|
247
|
+
list of organization members.
|
|
225
248
|
|
|
226
249
|
## Errors from a deployed app
|
|
227
250
|
|