@xano/cli 1.0.4-beta.4 → 1.0.4-beta.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.
package/README.md CHANGED
@@ -577,11 +577,14 @@ xano static_host build pull default --env prod -d ./prod-release
577
577
 
578
578
  # Push a build (name optional — auto-generated from the timestamp if omitted).
579
579
  # Accepts a directory (-d) or a zip file (-f). Defaults to the current directory.
580
+ # When pushing a directory, files matched by its .gitignore are skipped by default
581
+ # (the .git/ folder is always excluded); use --no-gitignore to push everything.
580
582
  # For package.json builds, the CLI waits for the build to finish (--no-wait to skip).
581
583
  xano static_host build push default -d ./dist -n "v1.0.0"
582
584
  xano static_host build push default # current dir, auto-name
583
585
  xano static_host build push default -f ./build.zip -n "v1.0.0" # from zip file
584
586
  xano static_host build push default -n "release" --description "Production build"
587
+ xano static_host build push default -d ./static --no-gitignore # push gitignored files too
585
588
 
586
589
  # Delete a build (prompts for confirmation; --force to skip)
587
590
  xano static_host build delete default --build_id 52
@@ -10,6 +10,7 @@ export default class StaticHostBuildPush extends BaseCommand {
10
10
  directory: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
11
  file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
12
  name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ 'no-gitignore': import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
14
  'no-wait': import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
15
  output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
15
16
  workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -18,6 +19,5 @@ export default class StaticHostBuildPush extends BaseCommand {
18
19
  verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
19
20
  };
20
21
  run(): Promise<void>;
21
- private countFiles;
22
22
  private createZipBuffer;
23
23
  }
@@ -3,6 +3,7 @@ import archiver from 'archiver';
3
3
  import * as fs from 'node:fs';
4
4
  import * as path from 'node:path';
5
5
  import BaseCommand from '../../../../base-command.js';
6
+ import { collectStaticHostFiles } from '../../../../utils/static-host-files.js';
6
7
  import { generateBuildName } from '../create/index.js';
7
8
  export default class StaticHostBuildPush extends BaseCommand {
8
9
  static args = {
@@ -27,6 +28,9 @@ ID: 124
27
28
  `$ xano static_host build push myhost -n "production" --description "Production build" -w 40
28
29
  Pushed 22 files as build "production"
29
30
  ID: 125
31
+ `,
32
+ `$ xano static_host build push default -d ./static --no-gitignore
33
+ Pushed 30 files as build "20260531-143022"
30
34
  `,
31
35
  ];
32
36
  static flags = {
@@ -52,6 +56,11 @@ ID: 125
52
56
  description: 'Build name (auto-generated from the current timestamp if omitted)',
53
57
  required: false,
54
58
  }),
59
+ 'no-gitignore': Flags.boolean({
60
+ default: false,
61
+ description: 'Push every file in the directory, including those matched by .gitignore (the .git/ folder is always excluded)',
62
+ required: false,
63
+ }),
55
64
  'no-wait': Flags.boolean({
56
65
  default: false,
57
66
  description: 'Return immediately after upload instead of waiting for the build to finish',
@@ -113,10 +122,15 @@ ID: 125
113
122
  if (!dirStats.isDirectory()) {
114
123
  this.error(`Path is not a directory: ${sourceDir}`);
115
124
  }
116
- fileCount = this.countFiles(sourceDir);
125
+ const files = collectStaticHostFiles(sourceDir, { respectGitignore: !flags['no-gitignore'] });
126
+ if (files.length === 0) {
127
+ this.error(`No files to push from ${sourceDir} — everything is excluded by .gitignore. ` +
128
+ `Use --no-gitignore to push the entire directory.`);
129
+ }
130
+ fileCount = files.length;
117
131
  if (animate)
118
132
  ux.action.start('Packaging', `${fileCount} files`);
119
- zipBuffer = await this.createZipBuffer(sourceDir);
133
+ zipBuffer = await this.createZipBuffer(sourceDir, files);
120
134
  if (animate) {
121
135
  ux.action.stop(`${fileCount} files (${(zipBuffer.length / (1024 * 1024)).toFixed(1)} MB)`);
122
136
  ux.action.start('Uploading');
@@ -192,20 +206,7 @@ ID: 125
192
206
  }
193
207
  }
194
208
  }
195
- countFiles(dir) {
196
- let count = 0;
197
- const entries = fs.readdirSync(dir, { withFileTypes: true });
198
- for (const entry of entries) {
199
- if (entry.isDirectory()) {
200
- count += this.countFiles(path.join(dir, entry.name));
201
- }
202
- else if (entry.isFile()) {
203
- count++;
204
- }
205
- }
206
- return count;
207
- }
208
- async createZipBuffer(sourceDir) {
209
+ async createZipBuffer(sourceDir, files) {
209
210
  return new Promise((resolve, reject) => {
210
211
  const chunks = [];
211
212
  const archive = archiver('zip', { zlib: { level: 9 } });
@@ -218,7 +219,9 @@ ID: 125
218
219
  archive.on('error', (err) => {
219
220
  reject(err);
220
221
  });
221
- archive.directory(sourceDir, false);
222
+ for (const rel of files) {
223
+ archive.file(path.join(sourceDir, rel), { name: rel });
224
+ }
222
225
  archive.finalize();
223
226
  });
224
227
  }
@@ -0,0 +1,21 @@
1
+ export interface CollectStaticHostFilesOptions {
2
+ /**
3
+ * When true, skip files matched by the source directory's `.gitignore`.
4
+ * The `.git/` folder is always excluded regardless of this setting.
5
+ */
6
+ respectGitignore: boolean;
7
+ }
8
+ /**
9
+ * Collect the files to upload for a static-host build, as POSIX-relative paths
10
+ * (sorted, for a deterministic archive).
11
+ *
12
+ * Honours the `.gitignore` at the root of `sourceDir` when `respectGitignore` is
13
+ * set, and always excludes the `.git/` directory — it is repo metadata, never
14
+ * deployable output.
15
+ *
16
+ * Directory rules in `.gitignore` carry a trailing slash (e.g. `node_modules/`),
17
+ * and the `ignore` package only matches those when the tested path also ends in a
18
+ * slash — so directories are tested as `${rel}/` (which also lets us prune the
19
+ * whole subtree) while files are tested as-is.
20
+ */
21
+ export declare function collectStaticHostFiles(sourceDir: string, options: CollectStaticHostFilesOptions): string[];
@@ -0,0 +1,41 @@
1
+ import ignore from 'ignore';
2
+ import * as fs from 'node:fs';
3
+ import { join, relative, sep } from 'node:path';
4
+ /**
5
+ * Collect the files to upload for a static-host build, as POSIX-relative paths
6
+ * (sorted, for a deterministic archive).
7
+ *
8
+ * Honours the `.gitignore` at the root of `sourceDir` when `respectGitignore` is
9
+ * set, and always excludes the `.git/` directory — it is repo metadata, never
10
+ * deployable output.
11
+ *
12
+ * Directory rules in `.gitignore` carry a trailing slash (e.g. `node_modules/`),
13
+ * and the `ignore` package only matches those when the tested path also ends in a
14
+ * slash — so directories are tested as `${rel}/` (which also lets us prune the
15
+ * whole subtree) while files are tested as-is.
16
+ */
17
+ export function collectStaticHostFiles(sourceDir, options) {
18
+ const ig = ignore().add('.git');
19
+ if (options.respectGitignore) {
20
+ const gitignorePath = join(sourceDir, '.gitignore');
21
+ if (fs.existsSync(gitignorePath)) {
22
+ ig.add(fs.readFileSync(gitignorePath, 'utf8'));
23
+ }
24
+ }
25
+ const files = [];
26
+ const walk = (dir) => {
27
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
28
+ const abs = join(dir, entry.name);
29
+ const rel = relative(sourceDir, abs).split(sep).join('/');
30
+ if (entry.isDirectory()) {
31
+ if (!ig.ignores(`${rel}/`))
32
+ walk(abs);
33
+ }
34
+ else if (entry.isFile() && !ig.ignores(rel)) {
35
+ files.push(rel);
36
+ }
37
+ }
38
+ };
39
+ walk(sourceDir);
40
+ return files.sort();
41
+ }