@fluixi/cli 0.1.0-alpha.81 → 0.1.0-alpha.83

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 (57) hide show
  1. package/README.md +21 -3
  2. package/dist/add.cjs +590 -0
  3. package/dist/add.d.ts +10 -0
  4. package/dist/add.d.ts.map +1 -0
  5. package/dist/add.js +107 -0
  6. package/dist/add.mjs +562 -0
  7. package/dist/check.cjs +102 -0
  8. package/dist/check.d.ts +13 -0
  9. package/dist/check.d.ts.map +1 -0
  10. package/dist/check.js +88 -0
  11. package/dist/check.mjs +76 -0
  12. package/dist/cli.cjs +977 -78
  13. package/dist/cli.js +44 -2
  14. package/dist/cli.mjs +976 -77
  15. package/dist/doctor.cjs +119 -0
  16. package/dist/doctor.d.ts +13 -0
  17. package/dist/doctor.d.ts.map +1 -0
  18. package/dist/doctor.js +114 -0
  19. package/dist/doctor.mjs +92 -0
  20. package/dist/generate.cjs +614 -65
  21. package/dist/generate.d.ts.map +1 -1
  22. package/dist/generate.js +118 -48
  23. package/dist/generate.mjs +619 -64
  24. package/dist/index.cjs +1 -1
  25. package/dist/index.mjs +1 -1
  26. package/dist/report.cjs +54 -0
  27. package/dist/report.d.ts +5 -0
  28. package/dist/report.d.ts.map +1 -0
  29. package/dist/report.js +31 -0
  30. package/dist/report.mjs +28 -0
  31. package/dist/scaffold/auth.cjs +422 -0
  32. package/dist/scaffold/auth.d.ts +39 -0
  33. package/dist/scaffold/auth.d.ts.map +1 -0
  34. package/dist/scaffold/auth.js +137 -0
  35. package/dist/scaffold/auth.mjs +389 -0
  36. package/dist/scaffold/edit.cjs +153 -0
  37. package/dist/scaffold/edit.d.ts +35 -0
  38. package/dist/scaffold/edit.d.ts.map +1 -0
  39. package/dist/scaffold/edit.js +140 -0
  40. package/dist/scaffold/edit.mjs +124 -0
  41. package/dist/scaffold/index.cjs +610 -0
  42. package/dist/scaffold/index.d.ts +10 -0
  43. package/dist/scaffold/index.d.ts.map +1 -0
  44. package/dist/scaffold/index.js +9 -0
  45. package/dist/scaffold/index.mjs +569 -0
  46. package/dist/scaffold/prompt.cjs +108 -0
  47. package/dist/scaffold/prompt.d.ts +9 -0
  48. package/dist/scaffold/prompt.d.ts.map +1 -0
  49. package/dist/scaffold/prompt.js +95 -0
  50. package/dist/scaffold/prompt.mjs +81 -0
  51. package/dist/scaffold/templates.cjs +274 -0
  52. package/dist/scaffold/templates.d.ts +26 -0
  53. package/dist/scaffold/templates.d.ts.map +1 -0
  54. package/dist/scaffold/templates.js +216 -0
  55. package/dist/scaffold/templates.mjs +229 -0
  56. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  57. package/package.json +19 -3
@@ -0,0 +1,35 @@
1
+ /** One thing done to a file, or the lines the author has to add themselves. */
2
+ export type Step = {
3
+ kind: 'created' | 'updated' | 'kept' | 'listed';
4
+ file: string;
5
+ detail?: string;
6
+ } | {
7
+ kind: 'hint';
8
+ file: string;
9
+ lines: string[];
10
+ };
11
+ /** After the last import statement, or at the top. */
12
+ export declare function addImport(text: string, line: string): string;
13
+ export interface Registration {
14
+ /** The import line, written as it should appear. */
15
+ importLine: string;
16
+ /** What goes in `imports: [...]`. */
17
+ entry: string;
18
+ /** Extra imports the entry needs, such as the config a condition reads. */
19
+ extraImports?: string[];
20
+ }
21
+ /**
22
+ * List a module in `src/app.config.ts` or `app.config.server.ts`, creating the file when the
23
+ * app has none. Returns false when it could only hint.
24
+ */
25
+ export declare function register(log: Step[], file: string, header: string, r: Registration): boolean;
26
+ /** Names already in `env.public` in fluixi.config, or null when there is no such list. */
27
+ export declare function envPublicNames(configFile: string): string[] | null;
28
+ /**
29
+ * Add names to `env.public`, skipping those already there. A `defineConfig({ ... })` with no
30
+ * `env` block gets one, as the first property.
31
+ */
32
+ export declare function addEnvPublic(log: Step[], configFile: string, names: string[]): void;
33
+ /** Append variables `.env.example` does not list yet, under a comment. */
34
+ export declare function addEnvExample(log: Step[], file: string, comment: string, vars: Record<string, string>): void;
35
+ //# sourceMappingURL=edit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edit.d.ts","sourceRoot":"","sources":["../../src/scaffold/edit.ts"],"names":[],"mappings":"AAaA,+EAA+E;AAC/E,MAAM,MAAM,IAAI,GACZ;IAAE,IAAI,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAClF;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAqCpD,sDAAsD;AACtD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAK5D;AAED,MAAM,WAAW,YAAY;IAC3B,oDAAoD;IACpD,UAAU,EAAE,MAAM,CAAC;IACnB,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,YAAY,GAAG,OAAO,CA0B5F;AAED,0FAA0F;AAC1F,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAQlE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAqBnF;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAQ5G"}
@@ -0,0 +1,140 @@
1
+ /**
2
+ * The few edits a generator makes to files the app already has.
3
+ *
4
+ * Text insertions at known shapes, never a parse and reprint: `app.config.ts` and
5
+ * `fluixi.config.ts` are the author's code, and reprinting them loses their formatting. When a
6
+ * file does not have the shape looked for, the lines to add are returned instead.
7
+ *
8
+ * Nothing here prints or depends on a package: `fluixi g`, `fluixi add` and create-fluixi all
9
+ * run this code and each reports the steps its own way.
10
+ */
11
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
12
+ import { dirname } from 'node:path';
13
+ /** Index just past the bracket matching the one at `open`, skipping strings and comments. */
14
+ function closing(text, open) {
15
+ let depth = 0;
16
+ for (let i = open; i < text.length; i++) {
17
+ const ch = text[i];
18
+ if (ch === '/' && text[i + 1] === '/')
19
+ i = text.indexOf('\n', i) === -1 ? text.length : text.indexOf('\n', i);
20
+ else if (ch === '/' && text[i + 1] === '*')
21
+ i = text.indexOf('*/', i + 2) + 1;
22
+ else if (ch === "'" || ch === '"' || ch === '`') {
23
+ for (i++; i < text.length && text[i] !== ch; i++)
24
+ if (text[i] === '\\')
25
+ i++;
26
+ }
27
+ else if ('([{'.includes(ch))
28
+ depth++;
29
+ else if (')]}'.includes(ch) && --depth === 0)
30
+ return i;
31
+ }
32
+ return -1;
33
+ }
34
+ /**
35
+ * Add items to the array literal at `open`, in its layout: one per line when it is written
36
+ * that way, inline otherwise.
37
+ */
38
+ function appendToArray(text, open, items) {
39
+ const close = closing(text, open);
40
+ const inner = text.slice(open + 1, close);
41
+ if (!inner.trim())
42
+ return text.slice(0, open + 1) + items.join(', ') + text.slice(close);
43
+ if (inner.includes('\n')) {
44
+ const last = inner.trimEnd();
45
+ const indent = last.slice(last.lastIndexOf('\n') + 1).match(/^\s*/)[0];
46
+ const comma = last.endsWith(',') ? '' : ',';
47
+ const tail = inner.slice(last.length);
48
+ return text.slice(0, open + 1) + last + comma + items.map((i) => `\n${indent}${i},`).join('') + tail + text.slice(close);
49
+ }
50
+ const body = inner.replace(/,?\s*$/, '');
51
+ const trailing = inner.slice(body.length).replace(/^,/, '');
52
+ return text.slice(0, open + 1) + body + ', ' + items.join(', ') + trailing + text.slice(close);
53
+ }
54
+ /** After the last import statement, or at the top. */
55
+ export function addImport(text, line) {
56
+ if (text.includes(line))
57
+ return text;
58
+ const imports = [...text.matchAll(/^import[\s\S]*?from\s+['"][^'"]+['"];?[^\n]*\n/gm)];
59
+ const at = imports.length ? imports[imports.length - 1].index + imports[imports.length - 1][0].length : 0;
60
+ return text.slice(0, at) + line + '\n' + text.slice(at);
61
+ }
62
+ /**
63
+ * List a module in `src/app.config.ts` or `app.config.server.ts`, creating the file when the
64
+ * app has none. Returns false when it could only hint.
65
+ */
66
+ export function register(log, file, header, r) {
67
+ const imports = [...(r.extraImports ?? []), r.importLine];
68
+ if (!existsSync(file)) {
69
+ mkdirSync(dirname(file), { recursive: true });
70
+ writeFileSync(file, `${header}import { defineApp } from '@fluixi/start/app';\n${imports.join('\n')}\n\nexport default defineApp({ imports: [${r.entry}] });\n`);
71
+ log.push({ kind: 'created', file });
72
+ return true;
73
+ }
74
+ let text = readFileSync(file, 'utf8');
75
+ if (text.includes(r.importLine)) {
76
+ log.push({ kind: 'listed', file, detail: r.entry });
77
+ return true;
78
+ }
79
+ const m = text.match(/\bimports\s*:\s*\[/);
80
+ if (!m) {
81
+ log.push({ kind: 'hint', file, lines: [...imports, `imports: [${r.entry}]`] });
82
+ return false;
83
+ }
84
+ text = appendToArray(text, m.index + m[0].length - 1, [r.entry]);
85
+ for (const line of imports)
86
+ text = addImport(text, line);
87
+ writeFileSync(file, text);
88
+ log.push({ kind: 'updated', file, detail: `imports ${r.entry}` });
89
+ return true;
90
+ }
91
+ /** Names already in `env.public` in fluixi.config, or null when there is no such list. */
92
+ export function envPublicNames(configFile) {
93
+ if (!existsSync(configFile))
94
+ return null;
95
+ const text = readFileSync(configFile, 'utf8');
96
+ const m = text.match(/\bpublic\s*:\s*\[/);
97
+ if (!m)
98
+ return null;
99
+ const open = m.index + m[0].length - 1;
100
+ const list = text.slice(open, closing(text, open) + 1);
101
+ return [...list.matchAll(/['"]([A-Za-z_][A-Za-z0-9_]*)['"]/g)].map((x) => x[1]);
102
+ }
103
+ /**
104
+ * Add names to `env.public`, skipping those already there. A `defineConfig({ ... })` with no
105
+ * `env` block gets one, as the first property.
106
+ */
107
+ export function addEnvPublic(log, configFile, names) {
108
+ const list = (indent) => `${indent}public: [\n${names.map((n) => `${indent} '${n}',\n`).join('')}${indent}],\n`;
109
+ const have = envPublicNames(configFile);
110
+ if (have === null) {
111
+ const text = existsSync(configFile) ? readFileSync(configFile, 'utf8') : '';
112
+ const call = text.match(/defineConfig\(\{\n/);
113
+ if (call && !/\benv\s*:/.test(text)) {
114
+ const at = call.index + call[0].length;
115
+ writeFileSync(configFile, text.slice(0, at) + ` env: {\n${list(' ')} },\n` + text.slice(at));
116
+ log.push({ kind: 'updated', file: configFile, detail: `env.public ${names.join(', ')}` });
117
+ return;
118
+ }
119
+ log.push({ kind: 'hint', file: configFile, lines: [`env: { public: [${names.map((n) => `'${n}'`).join(', ')}] },`] });
120
+ return;
121
+ }
122
+ const missing = names.filter((n) => !have.includes(n));
123
+ if (!missing.length)
124
+ return;
125
+ const text = readFileSync(configFile, 'utf8');
126
+ const m = text.match(/\bpublic\s*:\s*\[/);
127
+ writeFileSync(configFile, appendToArray(text, m.index + m[0].length - 1, missing.map((n) => `'${n}'`)));
128
+ log.push({ kind: 'updated', file: configFile, detail: `env.public + ${missing.join(', ')}` });
129
+ }
130
+ /** Append variables `.env.example` does not list yet, under a comment. */
131
+ export function addEnvExample(log, file, comment, vars) {
132
+ const text = existsSync(file) ? readFileSync(file, 'utf8') : '';
133
+ const have = new Set([...text.matchAll(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/gm)].map((m) => m[1]));
134
+ const add = Object.entries(vars).filter(([k]) => !have.has(k) && !have.has(`VITE_${k}`));
135
+ if (!add.length)
136
+ return;
137
+ const block = `${comment.split('\n').map((l) => `# ${l}`).join('\n')}\n${add.map(([k, v]) => `${k}=${v}`).join('\n')}\n`;
138
+ writeFileSync(file, text ? `${text.replace(/\n*$/, '\n')}\n${block}` : block);
139
+ log.push({ kind: text ? 'updated' : 'created', file, detail: add.map(([k]) => k).join(', ') });
140
+ }
@@ -0,0 +1,124 @@
1
+ // src/scaffold/edit.ts
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { dirname } from "node:path";
4
+ function closing(text, open) {
5
+ let depth = 0;
6
+ for (let i = open; i < text.length; i++) {
7
+ const ch = text[i];
8
+ if (ch === "/" && text[i + 1] === "/") i = text.indexOf("\n", i) === -1 ? text.length : text.indexOf("\n", i);
9
+ else if (ch === "/" && text[i + 1] === "*") i = text.indexOf("*/", i + 2) + 1;
10
+ else if (ch === "'" || ch === '"' || ch === "`") {
11
+ for (i++; i < text.length && text[i] !== ch; i++) if (text[i] === "\\") i++;
12
+ } else if ("([{".includes(ch)) depth++;
13
+ else if (")]}".includes(ch) && --depth === 0) return i;
14
+ }
15
+ return -1;
16
+ }
17
+ function appendToArray(text, open, items) {
18
+ const close = closing(text, open);
19
+ const inner = text.slice(open + 1, close);
20
+ if (!inner.trim()) return text.slice(0, open + 1) + items.join(", ") + text.slice(close);
21
+ if (inner.includes("\n")) {
22
+ const last = inner.trimEnd();
23
+ const indent = last.slice(last.lastIndexOf("\n") + 1).match(/^\s*/)[0];
24
+ const comma = last.endsWith(",") ? "" : ",";
25
+ const tail = inner.slice(last.length);
26
+ return text.slice(0, open + 1) + last + comma + items.map((i) => `
27
+ ${indent}${i},`).join("") + tail + text.slice(close);
28
+ }
29
+ const body = inner.replace(/,?\s*$/, "");
30
+ const trailing = inner.slice(body.length).replace(/^,/, "");
31
+ return text.slice(0, open + 1) + body + ", " + items.join(", ") + trailing + text.slice(close);
32
+ }
33
+ function addImport(text, line) {
34
+ if (text.includes(line)) return text;
35
+ const imports = [...text.matchAll(/^import[\s\S]*?from\s+['"][^'"]+['"];?[^\n]*\n/gm)];
36
+ const at = imports.length ? imports[imports.length - 1].index + imports[imports.length - 1][0].length : 0;
37
+ return text.slice(0, at) + line + "\n" + text.slice(at);
38
+ }
39
+ function register(log, file, header, r) {
40
+ const imports = [...r.extraImports ?? [], r.importLine];
41
+ if (!existsSync(file)) {
42
+ mkdirSync(dirname(file), { recursive: true });
43
+ writeFileSync(
44
+ file,
45
+ `${header}import { defineApp } from '@fluixi/start/app';
46
+ ${imports.join("\n")}
47
+
48
+ export default defineApp({ imports: [${r.entry}] });
49
+ `
50
+ );
51
+ log.push({ kind: "created", file });
52
+ return true;
53
+ }
54
+ let text = readFileSync(file, "utf8");
55
+ if (text.includes(r.importLine)) {
56
+ log.push({ kind: "listed", file, detail: r.entry });
57
+ return true;
58
+ }
59
+ const m = text.match(/\bimports\s*:\s*\[/);
60
+ if (!m) {
61
+ log.push({ kind: "hint", file, lines: [...imports, `imports: [${r.entry}]`] });
62
+ return false;
63
+ }
64
+ text = appendToArray(text, m.index + m[0].length - 1, [r.entry]);
65
+ for (const line of imports) text = addImport(text, line);
66
+ writeFileSync(file, text);
67
+ log.push({ kind: "updated", file, detail: `imports ${r.entry}` });
68
+ return true;
69
+ }
70
+ function envPublicNames(configFile) {
71
+ if (!existsSync(configFile)) return null;
72
+ const text = readFileSync(configFile, "utf8");
73
+ const m = text.match(/\bpublic\s*:\s*\[/);
74
+ if (!m) return null;
75
+ const open = m.index + m[0].length - 1;
76
+ const list = text.slice(open, closing(text, open) + 1);
77
+ return [...list.matchAll(/['"]([A-Za-z_][A-Za-z0-9_]*)['"]/g)].map((x) => x[1]);
78
+ }
79
+ function addEnvPublic(log, configFile, names) {
80
+ const list = (indent) => `${indent}public: [
81
+ ${names.map((n) => `${indent} '${n}',
82
+ `).join("")}${indent}],
83
+ `;
84
+ const have = envPublicNames(configFile);
85
+ if (have === null) {
86
+ const text2 = existsSync(configFile) ? readFileSync(configFile, "utf8") : "";
87
+ const call = text2.match(/defineConfig\(\{\n/);
88
+ if (call && !/\benv\s*:/.test(text2)) {
89
+ const at = call.index + call[0].length;
90
+ writeFileSync(configFile, text2.slice(0, at) + ` env: {
91
+ ${list(" ")} },
92
+ ` + text2.slice(at));
93
+ log.push({ kind: "updated", file: configFile, detail: `env.public ${names.join(", ")}` });
94
+ return;
95
+ }
96
+ log.push({ kind: "hint", file: configFile, lines: [`env: { public: [${names.map((n) => `'${n}'`).join(", ")}] },`] });
97
+ return;
98
+ }
99
+ const missing = names.filter((n) => !have.includes(n));
100
+ if (!missing.length) return;
101
+ const text = readFileSync(configFile, "utf8");
102
+ const m = text.match(/\bpublic\s*:\s*\[/);
103
+ writeFileSync(configFile, appendToArray(text, m.index + m[0].length - 1, missing.map((n) => `'${n}'`)));
104
+ log.push({ kind: "updated", file: configFile, detail: `env.public + ${missing.join(", ")}` });
105
+ }
106
+ function addEnvExample(log, file, comment, vars) {
107
+ const text = existsSync(file) ? readFileSync(file, "utf8") : "";
108
+ const have = new Set([...text.matchAll(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/gm)].map((m) => m[1]));
109
+ const add = Object.entries(vars).filter(([k]) => !have.has(k) && !have.has(`VITE_${k}`));
110
+ if (!add.length) return;
111
+ const block = `${comment.split("\n").map((l) => `# ${l}`).join("\n")}
112
+ ${add.map(([k, v]) => `${k}=${v}`).join("\n")}
113
+ `;
114
+ writeFileSync(file, text ? `${text.replace(/\n*$/, "\n")}
115
+ ${block}` : block);
116
+ log.push({ kind: text ? "updated" : "created", file, detail: add.map(([k]) => k).join(", ") });
117
+ }
118
+ export {
119
+ addEnvExample,
120
+ addEnvPublic,
121
+ addImport,
122
+ envPublicNames,
123
+ register
124
+ };