@fluojs/cli 1.0.0-beta.1 → 1.0.0-beta.3

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 (80) hide show
  1. package/README.ko.md +56 -7
  2. package/README.md +56 -7
  3. package/dist/cli.d.ts +12 -2
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +69 -33
  6. package/dist/commands/generate.d.ts +11 -1
  7. package/dist/commands/generate.d.ts.map +1 -1
  8. package/dist/commands/generate.js +71 -2
  9. package/dist/commands/inspect.d.ts +20 -0
  10. package/dist/commands/inspect.d.ts.map +1 -1
  11. package/dist/commands/inspect.js +153 -31
  12. package/dist/commands/migrate.d.ts.map +1 -1
  13. package/dist/commands/migrate.js +42 -0
  14. package/dist/commands/new.d.ts.map +1 -1
  15. package/dist/commands/new.js +25 -1
  16. package/dist/generator-types.d.ts +15 -1
  17. package/dist/generator-types.d.ts.map +1 -1
  18. package/dist/generators/controller.d.ts +7 -0
  19. package/dist/generators/controller.d.ts.map +1 -1
  20. package/dist/generators/controller.js +8 -0
  21. package/dist/generators/guard.d.ts +6 -0
  22. package/dist/generators/guard.d.ts.map +1 -1
  23. package/dist/generators/guard.js +7 -0
  24. package/dist/generators/interceptor.d.ts +6 -0
  25. package/dist/generators/interceptor.d.ts.map +1 -1
  26. package/dist/generators/interceptor.js +7 -0
  27. package/dist/generators/manifest.d.ts +267 -0
  28. package/dist/generators/manifest.d.ts.map +1 -1
  29. package/dist/generators/manifest.js +86 -1
  30. package/dist/generators/middleware.d.ts +6 -0
  31. package/dist/generators/middleware.d.ts.map +1 -1
  32. package/dist/generators/middleware.js +7 -0
  33. package/dist/generators/module.d.ts +22 -0
  34. package/dist/generators/module.d.ts.map +1 -1
  35. package/dist/generators/module.js +25 -0
  36. package/dist/generators/render.d.ts +7 -0
  37. package/dist/generators/render.d.ts.map +1 -1
  38. package/dist/generators/render.js +8 -0
  39. package/dist/generators/repository.d.ts +7 -0
  40. package/dist/generators/repository.d.ts.map +1 -1
  41. package/dist/generators/repository.js +8 -0
  42. package/dist/generators/request-dto.d.ts +6 -0
  43. package/dist/generators/request-dto.d.ts.map +1 -1
  44. package/dist/generators/request-dto.js +7 -0
  45. package/dist/generators/response-dto.d.ts +6 -0
  46. package/dist/generators/response-dto.d.ts.map +1 -1
  47. package/dist/generators/response-dto.js +7 -0
  48. package/dist/generators/service.d.ts +7 -0
  49. package/dist/generators/service.d.ts.map +1 -1
  50. package/dist/generators/service.js +8 -0
  51. package/dist/generators/utils.d.ts +18 -0
  52. package/dist/generators/utils.d.ts.map +1 -1
  53. package/dist/generators/utils.js +20 -0
  54. package/dist/help.d.ts +13 -0
  55. package/dist/help.d.ts.map +1 -1
  56. package/dist/help.js +15 -0
  57. package/dist/index.d.ts +1 -0
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +2 -1
  60. package/dist/new/package-spec-resolver.d.ts +7 -0
  61. package/dist/new/package-spec-resolver.d.ts.map +1 -1
  62. package/dist/new/package-spec-resolver.js +8 -0
  63. package/dist/new/prompt.d.ts +1 -0
  64. package/dist/new/prompt.d.ts.map +1 -1
  65. package/dist/new/prompt.js +12 -4
  66. package/dist/new/scaffold.d.ts.map +1 -1
  67. package/dist/new/scaffold.js +17 -1
  68. package/dist/prompt-cancel.d.ts +44 -0
  69. package/dist/prompt-cancel.d.ts.map +1 -0
  70. package/dist/prompt-cancel.js +49 -0
  71. package/dist/registry.d.ts +4 -1
  72. package/dist/registry.d.ts.map +1 -1
  73. package/dist/registry.js +13 -5
  74. package/dist/transforms/nestjs-migrate.d.ts +48 -0
  75. package/dist/transforms/nestjs-migrate.d.ts.map +1 -1
  76. package/dist/transforms/nestjs-migrate.js +62 -0
  77. package/dist/update-check.d.ts +82 -0
  78. package/dist/update-check.d.ts.map +1 -0
  79. package/dist/update-check.js +475 -0
  80. package/package.json +10 -2
@@ -1,8 +1,29 @@
1
1
  import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
2
2
  import { basename, extname, posix, resolve } from 'node:path';
3
3
  import ts from 'typescript';
4
+
5
+ /**
6
+ * Provides the migration transforms value.
7
+ */
4
8
  export const MIGRATION_TRANSFORMS = ['imports', 'injectable', 'scope', 'bootstrap', 'testing', 'tsconfig'];
9
+
10
+ /**
11
+ * Defines the migration transform kind type.
12
+ */
13
+
14
+ /**
15
+ * Provides the warning categories value.
16
+ */
5
17
  export const WARNING_CATEGORIES = ['inject-token', 'request-dto', 'pipe-converter', 'bootstrap-unsupported', 'testing-unsupported', 'import-unsupported', 'injectable-options', 'tsconfig-parse', 'bootstrap-port'];
18
+
19
+ /**
20
+ * Defines the warning category type.
21
+ */
22
+
23
+ /**
24
+ * Defines the migration warning type.
25
+ */
26
+
6
27
  const WARNING_CATEGORY_LABEL = {
7
28
  'inject-token': 'DI token migration (@Inject)',
8
29
  'request-dto': 'Request DTO migration (handler parameter decorators)',
@@ -14,9 +35,23 @@ const WARNING_CATEGORY_LABEL = {
14
35
  'tsconfig-parse': 'tsconfig parse failure',
15
36
  'bootstrap-port': 'Bootstrap port folding issue'
16
37
  };
38
+
39
+ /**
40
+ * Get warning category label.
41
+ *
42
+ * @param category The category.
43
+ * @returns The get warning category label result.
44
+ */
17
45
  export function getWarningCategoryLabel(category) {
18
46
  return WARNING_CATEGORY_LABEL[category];
19
47
  }
48
+
49
+ /**
50
+ * Group warnings by category.
51
+ *
52
+ * @param warnings The warnings.
53
+ * @returns The group warnings by category result.
54
+ */
20
55
  export function groupWarningsByCategory(warnings) {
21
56
  const groups = new Map();
22
57
  for (const warning of warnings) {
@@ -26,6 +61,19 @@ export function groupWarningsByCategory(warnings) {
26
61
  }
27
62
  return groups;
28
63
  }
64
+
65
+ /**
66
+ * Defines the file migration result type.
67
+ */
68
+
69
+ /**
70
+ * Defines the migration report type.
71
+ */
72
+
73
+ /**
74
+ * Defines the run nest js migration options type.
75
+ */
76
+
29
77
  const printer = ts.createPrinter({
30
78
  newLine: ts.NewLineKind.LineFeed
31
79
  });
@@ -841,6 +889,13 @@ function runTypeScriptTransforms(source, filePath, enabledTransforms) {
841
889
  warnings
842
890
  };
843
891
  }
892
+
893
+ /**
894
+ * Run nest js migration.
895
+ *
896
+ * @param options The options.
897
+ * @returns The run nest js migration result.
898
+ */
844
899
  export function runNestJsMigration(options) {
845
900
  const resolvedTargetPath = resolve(options.targetPath);
846
901
  if (!existsSync(resolvedTargetPath)) {
@@ -886,6 +941,13 @@ export function runNestJsMigration(options) {
886
941
  fileResults
887
942
  };
888
943
  }
944
+
945
+ /**
946
+ * Render transform list.
947
+ *
948
+ * @param kinds The kinds.
949
+ * @returns The render transform list result.
950
+ */
889
951
  export function renderTransformList(kinds) {
890
952
  return kinds.map(kind => `${kind} (${TRANSFORM_KIND_LABEL[kind]})`).join(', ');
891
953
  }
@@ -0,0 +1,82 @@
1
+ type CliStream = {
2
+ isTTY?: boolean;
3
+ write(message: string): unknown;
4
+ };
5
+ type CliReadableStream = {
6
+ isTTY?: boolean;
7
+ };
8
+ /**
9
+ * Defines the cli update check result type.
10
+ */
11
+ export type CliUpdateCheckResult = {
12
+ action: 'continue';
13
+ } | {
14
+ action: 'reran';
15
+ exitCode: number;
16
+ };
17
+ /**
18
+ * Defines the update install command type.
19
+ */
20
+ export type UpdateInstallCommand = {
21
+ args: string[];
22
+ command: string;
23
+ display: string;
24
+ };
25
+ /** Package manager that can install the CLI globally. */
26
+ export type UpdatePackageManager = 'bun' | 'npm' | 'pnpm' | 'yarn';
27
+ /**
28
+ * Defines the update command runtime type.
29
+ */
30
+ export type UpdateCommandRuntime = {
31
+ env: NodeJS.ProcessEnv;
32
+ stderr: CliStream;
33
+ };
34
+ /**
35
+ * Defines the update prompter type.
36
+ */
37
+ export type UpdatePrompter = {
38
+ confirm(message: string, defaultValue: boolean): Promise<boolean>;
39
+ };
40
+ /**
41
+ * Describes the cli update check runtime options contract.
42
+ */
43
+ export interface CliUpdateCheckRuntimeOptions {
44
+ cacheFile?: string;
45
+ cacheTtlMs?: number;
46
+ ci?: boolean;
47
+ currentVersion?: string;
48
+ env?: NodeJS.ProcessEnv;
49
+ fetchLatestVersion?: (packageName: string) => Promise<string | undefined>;
50
+ installPackage?: (installCommand: UpdateInstallCommand, runtime: UpdateCommandRuntime) => Promise<number>;
51
+ interactive?: boolean;
52
+ now?: () => Date;
53
+ packageName?: string;
54
+ packageManager?: UpdatePackageManager;
55
+ packageRoot?: string;
56
+ prompt?: UpdatePrompter;
57
+ rerunCli?: (argv: string[], runtime: UpdateCommandRuntime) => Promise<number>;
58
+ skip?: boolean;
59
+ stderr?: CliStream;
60
+ stdin?: CliReadableStream;
61
+ stdout?: CliStream;
62
+ }
63
+ /**
64
+ * Remove update check flags.
65
+ *
66
+ * @param argv The argv.
67
+ * @returns The remove update check flags result.
68
+ */
69
+ export declare function removeUpdateCheckFlags(argv: string[]): {
70
+ argv: string[];
71
+ skipUpdateCheck: boolean;
72
+ };
73
+ /**
74
+ * Run cli update check.
75
+ *
76
+ * @param argv The argv.
77
+ * @param options The options.
78
+ * @returns The run cli update check result.
79
+ */
80
+ export declare function runCliUpdateCheck(argv: string[], options?: CliUpdateCheckRuntimeOptions): Promise<CliUpdateCheckResult>;
81
+ export {};
82
+ //# sourceMappingURL=update-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-check.d.ts","sourceRoot":"","sources":["../src/update-check.ts"],"names":[],"mappings":"AAOA,KAAK,SAAS,GAAG;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC,CAAC;AAEF,KAAK,iBAAiB,GAAG;IACvB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAcF;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAC5B;IACE,MAAM,EAAE,UAAU,CAAC;CACpB,GACD;IACE,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEN;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,yDAAyD;AACzD,MAAM,MAAM,oBAAoB,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAEnE;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,MAAM,EAAE,SAAS,CAAC;CACnB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnE,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,kBAAkB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC1E,cAAc,CAAC,EAAE,CAAC,cAAc,EAAE,oBAAoB,EAAE,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1G,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,oBAAoB,CAAC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9E,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB;AAscD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG;IAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,eAAe,EAAE,OAAO,CAAA;CAAE,CAcnG;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,4BAAiC,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAkEjI"}
@@ -0,0 +1,475 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { basename, dirname, join, sep } from 'node:path';
5
+ import { createInterface } from 'node:readline/promises';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ /**
9
+ * Defines the cli update check result type.
10
+ */
11
+
12
+ /**
13
+ * Defines the update install command type.
14
+ */
15
+
16
+ /** Package manager that can install the CLI globally. */
17
+
18
+ /**
19
+ * Defines the update command runtime type.
20
+ */
21
+
22
+ /**
23
+ * Defines the update prompter type.
24
+ */
25
+
26
+ /**
27
+ * Describes the cli update check runtime options contract.
28
+ */
29
+
30
+ const DEFAULT_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
31
+ const DEFAULT_PACKAGE_NAME = '@fluojs/cli';
32
+ const DEFAULT_REGISTRY_TIMEOUT_MS = 5_000;
33
+ const UPDATE_CHECK_FLAGS = new Set(['--no-update-check', '--no-update-notifier']);
34
+ const UPDATE_PACKAGE_MANAGERS = new Set(['bun', 'npm', 'pnpm', 'yarn']);
35
+ function isRecord(value) {
36
+ return typeof value === 'object' && value !== null;
37
+ }
38
+ function isTruthyEnvValue(value) {
39
+ if (!value) {
40
+ return false;
41
+ }
42
+ return ['1', 'true', 'yes'].includes(value.toLowerCase());
43
+ }
44
+ function parseSemver(version) {
45
+ const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(version.trim());
46
+ if (!match) {
47
+ return undefined;
48
+ }
49
+ const [, major, minor, patch, prerelease] = match;
50
+ if (major === undefined || minor === undefined || patch === undefined) {
51
+ return undefined;
52
+ }
53
+ return {
54
+ major: Number.parseInt(major, 10),
55
+ minor: Number.parseInt(minor, 10),
56
+ patch: Number.parseInt(patch, 10),
57
+ prerelease: prerelease ? prerelease.split('.') : []
58
+ };
59
+ }
60
+ function compareNumericPart(left, right) {
61
+ if (left > right) {
62
+ return 1;
63
+ }
64
+ if (left < right) {
65
+ return -1;
66
+ }
67
+ return 0;
68
+ }
69
+ function comparePrereleaseIdentifier(left, right) {
70
+ if (left === undefined && right === undefined) {
71
+ return 0;
72
+ }
73
+ if (left === undefined) {
74
+ return -1;
75
+ }
76
+ if (right === undefined) {
77
+ return 1;
78
+ }
79
+ const leftNumber = /^\d+$/.test(left) ? Number.parseInt(left, 10) : undefined;
80
+ const rightNumber = /^\d+$/.test(right) ? Number.parseInt(right, 10) : undefined;
81
+ if (leftNumber !== undefined && rightNumber !== undefined) {
82
+ return compareNumericPart(leftNumber, rightNumber);
83
+ }
84
+ if (leftNumber !== undefined) {
85
+ return -1;
86
+ }
87
+ if (rightNumber !== undefined) {
88
+ return 1;
89
+ }
90
+ if (left > right) {
91
+ return 1;
92
+ }
93
+ if (left < right) {
94
+ return -1;
95
+ }
96
+ return 0;
97
+ }
98
+ function compareSemver(left, right) {
99
+ const major = compareNumericPart(left.major, right.major);
100
+ if (major !== 0) {
101
+ return major;
102
+ }
103
+ const minor = compareNumericPart(left.minor, right.minor);
104
+ if (minor !== 0) {
105
+ return minor;
106
+ }
107
+ const patch = compareNumericPart(left.patch, right.patch);
108
+ if (patch !== 0) {
109
+ return patch;
110
+ }
111
+ if (left.prerelease.length === 0 && right.prerelease.length === 0) {
112
+ return 0;
113
+ }
114
+ if (left.prerelease.length === 0) {
115
+ return 1;
116
+ }
117
+ if (right.prerelease.length === 0) {
118
+ return -1;
119
+ }
120
+ const maxLength = Math.max(left.prerelease.length, right.prerelease.length);
121
+ for (let index = 0; index < maxLength; index += 1) {
122
+ const comparison = comparePrereleaseIdentifier(left.prerelease[index], right.prerelease[index]);
123
+ if (comparison !== 0) {
124
+ return comparison;
125
+ }
126
+ }
127
+ return 0;
128
+ }
129
+ function isNewerVersion(latestVersion, currentVersion) {
130
+ const latest = parseSemver(latestVersion);
131
+ const current = parseSemver(currentVersion);
132
+ if (!latest || !current) {
133
+ return false;
134
+ }
135
+ return compareSemver(latest, current) > 0;
136
+ }
137
+ function parseCache(contents) {
138
+ const parsed = JSON.parse(contents);
139
+ if (!isRecord(parsed)) {
140
+ return undefined;
141
+ }
142
+ const checkedAt = parsed.checkedAt;
143
+ const latestVersion = parsed.latestVersion;
144
+ if (typeof checkedAt !== 'number' || typeof latestVersion !== 'string') {
145
+ return undefined;
146
+ }
147
+ return {
148
+ checkedAt,
149
+ latestVersion
150
+ };
151
+ }
152
+ function resolveCacheFile(env) {
153
+ const cacheRoot = env.XDG_CACHE_HOME ?? join(homedir(), '.cache');
154
+ return join(cacheRoot, 'fluo', 'cli-update-check.json');
155
+ }
156
+ async function readCachedLatestVersion(cacheFile, nowMs, cacheTtlMs) {
157
+ try {
158
+ const cache = parseCache(await readFile(cacheFile, 'utf8'));
159
+ if (!cache) {
160
+ return undefined;
161
+ }
162
+ if (nowMs - cache.checkedAt > cacheTtlMs) {
163
+ return undefined;
164
+ }
165
+ return cache.latestVersion;
166
+ } catch (error) {
167
+ if (error instanceof SyntaxError) {
168
+ return undefined;
169
+ }
170
+ return undefined;
171
+ }
172
+ }
173
+ async function writeLatestVersionCache(cacheFile, latestVersion, nowMs) {
174
+ await mkdir(dirname(cacheFile), {
175
+ recursive: true
176
+ });
177
+ await writeFile(cacheFile, `${JSON.stringify({
178
+ checkedAt: nowMs,
179
+ latestVersion
180
+ }, null, 2)}\n`, 'utf8');
181
+ }
182
+ async function fetchLatestDistTag(packageName) {
183
+ const controller = new AbortController();
184
+ const timeout = setTimeout(() => controller.abort(), DEFAULT_REGISTRY_TIMEOUT_MS);
185
+ try {
186
+ const encodedPackageName = encodeURIComponent(packageName);
187
+ const response = await fetch(`https://registry.npmjs.org/-/package/${encodedPackageName}/dist-tags`, {
188
+ headers: {
189
+ accept: 'application/json'
190
+ },
191
+ signal: controller.signal
192
+ });
193
+ if (!response.ok) {
194
+ return undefined;
195
+ }
196
+ const payload = await response.json();
197
+ if (!isRecord(payload) || typeof payload.latest !== 'string') {
198
+ return undefined;
199
+ }
200
+ return payload.latest;
201
+ } catch (_error) {
202
+ return undefined;
203
+ } finally {
204
+ clearTimeout(timeout);
205
+ }
206
+ }
207
+ async function resolveLatestVersion(packageName, cacheFile, cacheTtlMs, nowMs, fetchLatestVersion) {
208
+ const cachedLatestVersion = await readCachedLatestVersion(cacheFile, nowMs, cacheTtlMs);
209
+ if (cachedLatestVersion) {
210
+ return cachedLatestVersion;
211
+ }
212
+ let latestVersion;
213
+ try {
214
+ latestVersion = await fetchLatestVersion(packageName);
215
+ } catch (_error) {
216
+ return undefined;
217
+ }
218
+ if (!latestVersion) {
219
+ return undefined;
220
+ }
221
+ try {
222
+ await writeLatestVersionCache(cacheFile, latestVersion, nowMs);
223
+ } catch (_error) {
224
+ return latestVersion;
225
+ }
226
+ return latestVersion;
227
+ }
228
+ async function readOwnPackageVersion() {
229
+ const packageJsonPath = fileURLToPath(new URL('../package.json', import.meta.url));
230
+ try {
231
+ const manifest = JSON.parse(await readFile(packageJsonPath, 'utf8'));
232
+ if (!isRecord(manifest) || typeof manifest.version !== 'string') {
233
+ return undefined;
234
+ }
235
+ return manifest.version;
236
+ } catch (_error) {
237
+ return undefined;
238
+ }
239
+ }
240
+ function normalizePathForDetection(path) {
241
+ return (path ?? '').replaceAll('\\', '/').toLowerCase();
242
+ }
243
+ function parsePackageManagerName(value) {
244
+ if (!value) {
245
+ return undefined;
246
+ }
247
+ const normalized = value.trim().toLowerCase();
248
+ if (UPDATE_PACKAGE_MANAGERS.has(normalized)) {
249
+ return normalized;
250
+ }
251
+ return undefined;
252
+ }
253
+ function parsePackageManagerFromUserAgent(userAgent) {
254
+ const name = userAgent?.split(' ')[0]?.split('/')[0];
255
+ return parsePackageManagerName(name);
256
+ }
257
+ function parsePackageManagerFromExecPath(execPath) {
258
+ const executableName = basename(execPath ?? '', '.cmd').toLowerCase();
259
+ const packageManager = parsePackageManagerName(executableName.replace(/\.(?:cjs|mjs|js)$/, ''));
260
+ if (packageManager) {
261
+ return packageManager;
262
+ }
263
+ const normalizedPath = normalizePathForDetection(execPath);
264
+ if (normalizedPath.includes('/pnpm/')) {
265
+ return 'pnpm';
266
+ }
267
+ if (normalizedPath.includes('/yarn/')) {
268
+ return 'yarn';
269
+ }
270
+ if (normalizedPath.includes('/bun/')) {
271
+ return 'bun';
272
+ }
273
+ if (normalizedPath.includes('/npm/')) {
274
+ return 'npm';
275
+ }
276
+ return undefined;
277
+ }
278
+ function parsePackageManagerFromPackageRoot(packageRoot) {
279
+ const normalizedPath = normalizePathForDetection(`${sep}${packageRoot}${sep}`);
280
+ if (normalizedPath.includes('/.pnpm/') || normalizedPath.includes('/pnpm/global/')) {
281
+ return 'pnpm';
282
+ }
283
+ if (normalizedPath.includes('/.bun/install/global/') || normalizedPath.includes('/bun/install/global/')) {
284
+ return 'bun';
285
+ }
286
+ if (normalizedPath.includes('/.config/yarn/global/') || normalizedPath.includes('/yarn/global/')) {
287
+ return 'yarn';
288
+ }
289
+ if (normalizedPath.includes('/node_modules/')) {
290
+ return 'npm';
291
+ }
292
+ return undefined;
293
+ }
294
+ function resolveUpdatePackageManager(env, packageRoot) {
295
+ return parsePackageManagerName(env.FLUO_UPDATE_PACKAGE_MANAGER) ?? parsePackageManagerFromPackageRoot(packageRoot) ?? parsePackageManagerFromUserAgent(env.npm_config_user_agent) ?? parsePackageManagerFromExecPath(env.npm_execpath) ?? 'npm';
296
+ }
297
+ function resolveInstallCommand(packageName, latestVersion, packageManager) {
298
+ const packageSpecifier = `${packageName}@${latestVersion}`;
299
+ if (packageManager === 'bun') {
300
+ return {
301
+ args: ['add', '-g', packageSpecifier],
302
+ command: 'bun',
303
+ display: `bun add -g ${packageSpecifier}`
304
+ };
305
+ }
306
+ if (packageManager === 'pnpm') {
307
+ return {
308
+ args: ['add', '-g', packageSpecifier],
309
+ command: 'pnpm',
310
+ display: `pnpm add -g ${packageSpecifier}`
311
+ };
312
+ }
313
+ if (packageManager === 'yarn') {
314
+ return {
315
+ args: ['global', 'add', packageSpecifier],
316
+ command: 'yarn',
317
+ display: `yarn global add ${packageSpecifier}`
318
+ };
319
+ }
320
+ return {
321
+ args: ['install', '-g', packageSpecifier],
322
+ command: 'npm',
323
+ display: `npm install -g ${packageSpecifier}`
324
+ };
325
+ }
326
+ async function defaultPromptConfirm(message, defaultValue) {
327
+ const promptSuffix = defaultValue ? 'Y/n' : 'y/N';
328
+ const readline = createInterface({
329
+ input: process.stdin,
330
+ output: process.stdout
331
+ });
332
+ try {
333
+ const answer = (await readline.question(`${message} (${promptSuffix}) `)).trim().toLowerCase();
334
+ if (answer.length === 0) {
335
+ return defaultValue;
336
+ }
337
+ return answer === 'y' || answer === 'yes';
338
+ } finally {
339
+ readline.close();
340
+ }
341
+ }
342
+ async function defaultInstallPackage(installCommand, runtime) {
343
+ const result = spawnSync(installCommand.command, installCommand.args, {
344
+ env: runtime.env,
345
+ stdio: 'inherit'
346
+ });
347
+ if (result.error) {
348
+ runtime.stderr.write(`Failed to run ${installCommand.display}: ${result.error.message}\n`);
349
+ return 1;
350
+ }
351
+ return result.status ?? 1;
352
+ }
353
+ async function defaultRerunCli(argv, runtime) {
354
+ const command = process.platform === 'win32' ? 'fluo.cmd' : 'fluo';
355
+ const result = spawnSync(command, argv, {
356
+ env: {
357
+ ...runtime.env,
358
+ FLUO_UPDATE_CHECK_REEXEC: '1'
359
+ },
360
+ stdio: 'inherit'
361
+ });
362
+ if (result.error) {
363
+ runtime.stderr.write(`Failed to restart fluo after updating: ${result.error.message}\n`);
364
+ return 1;
365
+ }
366
+ return result.status ?? 1;
367
+ }
368
+ function shouldSkipForEnvironment(env, ci) {
369
+ return Boolean(ci) || isTruthyEnvValue(env.CI) || isTruthyEnvValue(env.GITHUB_ACTIONS) || isTruthyEnvValue(env.FLUO_NO_UPDATE_CHECK) || isTruthyEnvValue(env.NO_UPDATE_NOTIFIER) || isTruthyEnvValue(env.FLUO_UPDATE_CHECK_REEXEC) || Boolean(env.npm_lifecycle_event) || Boolean(env.npm_lifecycle_script);
370
+ }
371
+ function shouldRunInteractiveUpdateCheck(options, env) {
372
+ if (options.skip || options.interactive === false || shouldSkipForEnvironment(env, options.ci)) {
373
+ return false;
374
+ }
375
+ return Boolean(options.stdin?.isTTY ?? process.stdin.isTTY) && Boolean(options.stdout?.isTTY ?? process.stdout.isTTY);
376
+ }
377
+
378
+ /**
379
+ * Remove update check flags.
380
+ *
381
+ * @param argv The argv.
382
+ * @returns The remove update check flags result.
383
+ */
384
+ export function removeUpdateCheckFlags(argv) {
385
+ const filteredArgv = [];
386
+ let skipUpdateCheck = false;
387
+ for (const arg of argv) {
388
+ if (UPDATE_CHECK_FLAGS.has(arg)) {
389
+ skipUpdateCheck = true;
390
+ continue;
391
+ }
392
+ filteredArgv.push(arg);
393
+ }
394
+ return {
395
+ argv: filteredArgv,
396
+ skipUpdateCheck
397
+ };
398
+ }
399
+
400
+ /**
401
+ * Run cli update check.
402
+ *
403
+ * @param argv The argv.
404
+ * @param options The options.
405
+ * @returns The run cli update check result.
406
+ */
407
+ export async function runCliUpdateCheck(argv, options = {}) {
408
+ const env = options.env ?? {};
409
+ const stderr = options.stderr ?? process.stderr;
410
+ const stdout = options.stdout ?? process.stdout;
411
+ if (!shouldRunInteractiveUpdateCheck({
412
+ ...options,
413
+ stderr,
414
+ stdout
415
+ }, env)) {
416
+ return {
417
+ action: 'continue'
418
+ };
419
+ }
420
+ const packageName = options.packageName ?? DEFAULT_PACKAGE_NAME;
421
+ const now = options.now ?? (() => new Date());
422
+ const nowMs = now().getTime();
423
+ const cacheFile = options.cacheFile ?? resolveCacheFile(env);
424
+ const cacheTtlMs = options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
425
+ const currentVersion = options.currentVersion ?? (await readOwnPackageVersion());
426
+ const packageRoot = options.packageRoot ?? dirname(fileURLToPath(new URL('../package.json', import.meta.url)));
427
+ if (!currentVersion) {
428
+ return {
429
+ action: 'continue'
430
+ };
431
+ }
432
+ const latestVersion = await resolveLatestVersion(packageName, cacheFile, cacheTtlMs, nowMs, options.fetchLatestVersion ?? fetchLatestDistTag);
433
+ if (!latestVersion || !isNewerVersion(latestVersion, currentVersion)) {
434
+ return {
435
+ action: 'continue'
436
+ };
437
+ }
438
+ stderr.write(`A newer ${packageName} version is available: ${currentVersion} -> ${latestVersion}.\n`);
439
+ const prompt = options.prompt ?? {
440
+ confirm: defaultPromptConfirm
441
+ };
442
+ const shouldInstall = await prompt.confirm(`Install ${packageName}@${latestVersion} now and restart this command?`, false);
443
+ if (!shouldInstall) {
444
+ stderr.write(`Continuing with ${packageName}@${currentVersion}.\n`);
445
+ return {
446
+ action: 'continue'
447
+ };
448
+ }
449
+ const packageManager = options.packageManager ?? resolveUpdatePackageManager(env, packageRoot);
450
+ const installCommand = resolveInstallCommand(packageName, latestVersion, packageManager);
451
+ stderr.write(`Installing ${packageName}@${latestVersion} with \`${installCommand.display}\`...\n`);
452
+ const commandRuntime = {
453
+ env,
454
+ stderr
455
+ };
456
+ const installExitCode = await (options.installPackage ?? defaultInstallPackage)(installCommand, commandRuntime);
457
+ if (installExitCode !== 0) {
458
+ stderr.write(`Update install failed with exit code ${installExitCode}; continuing with ${packageName}@${currentVersion}.\n`);
459
+ return {
460
+ action: 'continue'
461
+ };
462
+ }
463
+ stderr.write(`Updated ${packageName} to ${latestVersion}. Restarting fluo...\n`);
464
+ const rerunExitCode = await (options.rerunCli ?? defaultRerunCli)(argv, {
465
+ env: {
466
+ ...env,
467
+ FLUO_UPDATE_CHECK_REEXEC: '1'
468
+ },
469
+ stderr
470
+ });
471
+ return {
472
+ action: 'reran',
473
+ exitCode: rerunExitCode
474
+ };
475
+ }
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "migration",
10
10
  "diagnostics"
11
11
  ],
12
- "version": "1.0.0-beta.1",
12
+ "version": "1.0.0-beta.3",
13
13
  "private": false,
14
14
  "license": "MIT",
15
15
  "repository": {
@@ -44,7 +44,15 @@
44
44
  "ejs": "^3.1.10",
45
45
  "tsx": "^4.20.4",
46
46
  "typescript": "^6.0.2",
47
- "@fluojs/runtime": "^1.0.0-beta.1"
47
+ "@fluojs/runtime": "^1.0.0-beta.9"
48
+ },
49
+ "peerDependencies": {
50
+ "@fluojs/studio": "^1.0.0-beta.3"
51
+ },
52
+ "peerDependenciesMeta": {
53
+ "@fluojs/studio": {
54
+ "optional": true
55
+ }
48
56
  },
49
57
  "devDependencies": {
50
58
  "@types/ejs": "^3.1.5",