@tatara/cli 0.7.0 → 0.8.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.mjs +157 -10
- package/package.json +1 -1
- package/templates/versions.json +6 -6
package/dist/bin.mjs
CHANGED
|
@@ -190,6 +190,18 @@ function ignored(v, text) {
|
|
|
190
190
|
return new RegExp(`tatara-check-ignore\\s+${v.rule}:\\s*\\S`).test(previous);
|
|
191
191
|
}
|
|
192
192
|
//#endregion
|
|
193
|
+
//#region src/project/ejected.ts
|
|
194
|
+
/** Packages the app ejected (`tatara eject`); their code lives in src/ejected/<name>. */
|
|
195
|
+
function ejectedPackages(config) {
|
|
196
|
+
return Object.keys(config.ejected ?? {});
|
|
197
|
+
}
|
|
198
|
+
/** Points `@tatara/<name>` and `@tatara/<name>/<entry>` imports at the app's ejected copy. */
|
|
199
|
+
function pointAtEjected(text, names) {
|
|
200
|
+
if (names.length === 0) return text;
|
|
201
|
+
const pattern = new RegExp(`(["'])@tatara/(${names.join("|")})(?:/([\\w-]+))?\\1`, "g");
|
|
202
|
+
return text.replace(pattern, (_match, quote, name, entry) => `${quote}#ejected/${name}/${entry ?? "index"}${quote}`);
|
|
203
|
+
}
|
|
204
|
+
//#endregion
|
|
193
205
|
//#region src/commands/sync.ts
|
|
194
206
|
var sync_exports = /* @__PURE__ */ __exportAll({
|
|
195
207
|
specifier: () => specifier,
|
|
@@ -235,19 +247,21 @@ async function writeIfChanged(path, content, written) {
|
|
|
235
247
|
}
|
|
236
248
|
async function sync(project = loadProject()) {
|
|
237
249
|
const { root } = project;
|
|
250
|
+
const ejected = ejectedPackages(project.config);
|
|
251
|
+
const write = (path, content, out) => writeIfChanged(path, pointAtEjected(content, ejected), out);
|
|
238
252
|
const out = join(root, "src/generated");
|
|
239
253
|
await mkdir(out, { recursive: true });
|
|
240
254
|
const written = [];
|
|
241
255
|
const dirs = ["src/core/*", "src/modules/*"];
|
|
242
256
|
const routes = await find(root, dirs.map((d) => `${d}/routes/*.routes.ts`));
|
|
243
|
-
await
|
|
257
|
+
await write(join(out, "routes.ts"), listModule(routes, {
|
|
244
258
|
prefix: "routes",
|
|
245
259
|
typeImport: "import type { RouteDefinition } from \"#core/http/routes\";",
|
|
246
260
|
typeName: "RouteDefinition",
|
|
247
261
|
exportName: "routes"
|
|
248
262
|
}), written);
|
|
249
263
|
const commands = await find(root, dirs.map((d) => `${d}/commands/*.ts`));
|
|
250
|
-
await
|
|
264
|
+
await write(join(out, "commands.ts"), listModule(commands, {
|
|
251
265
|
prefix: "command",
|
|
252
266
|
typeImport: "import type { CommandDefinition } from \"@tatara/core\";",
|
|
253
267
|
typeName: "CommandDefinition",
|
|
@@ -255,14 +269,14 @@ async function sync(project = loadProject()) {
|
|
|
255
269
|
}), written);
|
|
256
270
|
if (project.has("jobs")) {
|
|
257
271
|
const consumers = await find(root, dirs.map((d) => `${d}/consumers/*.ts`));
|
|
258
|
-
await
|
|
272
|
+
await write(join(out, "consumers.ts"), listModule(consumers, {
|
|
259
273
|
prefix: "consumer",
|
|
260
274
|
typeImport: "import type { ConsumerDefinition } from \"@tatara/jobs\";",
|
|
261
275
|
typeName: "ConsumerDefinition",
|
|
262
276
|
exportName: "consumers"
|
|
263
277
|
}), written);
|
|
264
278
|
const schedules = await find(root, dirs.map((d) => `${d}/schedules/*.ts`));
|
|
265
|
-
await
|
|
279
|
+
await write(join(out, "schedules.ts"), listModule(schedules, {
|
|
266
280
|
prefix: "schedule",
|
|
267
281
|
typeImport: "import type { ScheduleDefinition } from \"@tatara/jobs\";",
|
|
268
282
|
typeName: "ScheduleDefinition",
|
|
@@ -273,7 +287,7 @@ async function sync(project = loadProject()) {
|
|
|
273
287
|
const permissions = await find(root, ["src/modules/*/permissions/*.permissions.ts"]);
|
|
274
288
|
const imports = permissions.map((f) => `import ${identifier(f, "permissions")} from "${specifier(f)}";`);
|
|
275
289
|
const names = permissions.map((f) => identifier(f, "permissions"));
|
|
276
|
-
await
|
|
290
|
+
await write(join(out, "permissions.ts"), `${HEADER}import { authzPermissions, mergeCatalogs, type PermissionKeysOf } from "@tatara/authz";
|
|
277
291
|
${imports.join("\n")}${imports.length ? "\n" : ""}
|
|
278
292
|
export const catalog = mergeCatalogs(authzPermissions${names.map((n) => `, ${n}`).join("")});
|
|
279
293
|
|
|
@@ -283,7 +297,7 @@ export type PermissionKey =
|
|
|
283
297
|
}
|
|
284
298
|
if (project.config.database !== "none") {
|
|
285
299
|
const seeds = await find(root, dirs.map((d) => `${d}/db/seed.ts`));
|
|
286
|
-
await
|
|
300
|
+
await write(join(out, "seeds.ts"), listModule(seeds, {
|
|
287
301
|
prefix: "seed",
|
|
288
302
|
typeImport: "import type { SeedDefinition } from \"@tatara/db\";",
|
|
289
303
|
typeName: "SeedDefinition",
|
|
@@ -292,7 +306,7 @@ export type PermissionKey =
|
|
|
292
306
|
}
|
|
293
307
|
if (project.config.orm === "drizzle") {
|
|
294
308
|
const schemas = await find(root, dirs.map((d) => `${d}/db/*.schema.ts`));
|
|
295
|
-
await
|
|
309
|
+
await write(join(out, "schema.ts"), `${HEADER}${schemas.map((f) => `export * from "${specifier(f)}";`).join("\n")}\nexport {};\n`, written);
|
|
296
310
|
}
|
|
297
311
|
if (project.config.orm === "prisma") {
|
|
298
312
|
const bin = join(root, "node_modules/.bin/prisma");
|
|
@@ -650,6 +664,121 @@ function printDoctor(checks) {
|
|
|
650
664
|
}
|
|
651
665
|
}
|
|
652
666
|
//#endregion
|
|
667
|
+
//#region src/commands/eject.ts
|
|
668
|
+
var EjectError = class extends Error {};
|
|
669
|
+
const short = (name) => name.replace(/^@tatara\//, "");
|
|
670
|
+
const full = (name) => `@tatara/${short(name)}`;
|
|
671
|
+
const NOT_LIBRARIES = /* @__PURE__ */ new Set(["cli", "create-tatara"]);
|
|
672
|
+
async function readJson(path) {
|
|
673
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Copies a package's source into src/ejected/<name>, points the app's imports at it and records
|
|
677
|
+
* it in tatara.json. From then on the app owns that code and `tatara upgrade` leaves it alone.
|
|
678
|
+
*/
|
|
679
|
+
async function eject(project, name, opts = {}) {
|
|
680
|
+
const target = short(name);
|
|
681
|
+
if (NOT_LIBRARIES.has(target)) throw new EjectError(`${full(target)} is a tool, not a library`);
|
|
682
|
+
const appPkgPath = join(project.root, "package.json");
|
|
683
|
+
const appPkg = await readJson(appPkgPath);
|
|
684
|
+
const installed = Object.keys({
|
|
685
|
+
...appPkg.dependencies,
|
|
686
|
+
...appPkg.devDependencies
|
|
687
|
+
}).filter((dep) => dep.startsWith("@tatara/") && !NOT_LIBRARIES.has(short(dep))).map(short);
|
|
688
|
+
const already = ejectedPackages(project.config);
|
|
689
|
+
if (already.includes(target)) throw new EjectError(`${full(target)} is already ejected`);
|
|
690
|
+
if (!installed.includes(target)) throw new EjectError(`${full(target)} is not a dependency of this app`);
|
|
691
|
+
const manifests = /* @__PURE__ */ new Map();
|
|
692
|
+
for (const dep of installed) {
|
|
693
|
+
const path = join(project.root, "node_modules", full(dep), "package.json");
|
|
694
|
+
if (existsSync(path)) manifests.set(dep, await readJson(path));
|
|
695
|
+
}
|
|
696
|
+
const chain = [target];
|
|
697
|
+
for (let i = 0; i < chain.length; i++) for (const [dep, manifest] of manifests) {
|
|
698
|
+
if (chain.includes(dep)) continue;
|
|
699
|
+
if (full(chain[i]) in (manifest.peerDependencies ?? {})) chain.push(dep);
|
|
700
|
+
}
|
|
701
|
+
const dependents = chain.slice(1);
|
|
702
|
+
if (dependents.length > 0 && !opts.withDependents) throw new EjectError(`${dependents.map(full).join(", ")} depend on ${full(target)}. Eject them too with --with-dependents.`);
|
|
703
|
+
const report = {
|
|
704
|
+
ejected: chain,
|
|
705
|
+
copied: [],
|
|
706
|
+
rewritten: [],
|
|
707
|
+
added: []
|
|
708
|
+
};
|
|
709
|
+
const versions = {};
|
|
710
|
+
for (const dep of chain) {
|
|
711
|
+
const manifest = manifests.get(dep);
|
|
712
|
+
const source = join(project.root, "node_modules", full(dep), "src");
|
|
713
|
+
if (!manifest || !existsSync(source)) throw new EjectError(`${full(dep)} ships no source to eject; update it to a version that does, then run this again`);
|
|
714
|
+
versions[dep] = manifest.version;
|
|
715
|
+
const files = await glob(["**/*.ts"], {
|
|
716
|
+
cwd: source,
|
|
717
|
+
ignore: ["**/*.test.ts"]
|
|
718
|
+
});
|
|
719
|
+
for (const file of files) {
|
|
720
|
+
const to = `src/ejected/${dep}/${file}`;
|
|
721
|
+
await mkdir(dirname(join(project.root, to)), { recursive: true });
|
|
722
|
+
await cp(join(source, file), join(project.root, to));
|
|
723
|
+
report.copied.push(to);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
const ejected = [...already, ...chain];
|
|
727
|
+
for (const file of await glob(["src/**/*.ts", "tests/**/*.ts"], { cwd: project.root })) {
|
|
728
|
+
const path = join(project.root, file);
|
|
729
|
+
const text = await readFile(path, "utf8");
|
|
730
|
+
const next = pointAtEjected(text, ejected);
|
|
731
|
+
if (next !== text) {
|
|
732
|
+
await writeFile(path, next);
|
|
733
|
+
if (!report.copied.includes(file)) report.rewritten.push(file);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
const deps = { ...appPkg.dependencies };
|
|
737
|
+
const listed = new Set(Object.keys({
|
|
738
|
+
...appPkg.dependencies,
|
|
739
|
+
...appPkg.devDependencies
|
|
740
|
+
}));
|
|
741
|
+
for (const dep of chain) {
|
|
742
|
+
const manifest = manifests.get(dep);
|
|
743
|
+
for (const [peer, range] of Object.entries(manifest.peerDependencies ?? {})) {
|
|
744
|
+
if (listed.has(peer) || manifest.peerDependenciesMeta?.[peer]?.optional) continue;
|
|
745
|
+
if (peer.startsWith("@tatara/") && ejected.includes(short(peer))) continue;
|
|
746
|
+
deps[peer] = range;
|
|
747
|
+
listed.add(peer);
|
|
748
|
+
report.added.push(peer);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
for (const dep of chain) delete deps[full(dep)];
|
|
752
|
+
const devDeps = { ...appPkg.devDependencies };
|
|
753
|
+
for (const dep of chain) delete devDeps[full(dep)];
|
|
754
|
+
const nextPkg = {
|
|
755
|
+
...appPkg,
|
|
756
|
+
imports: {
|
|
757
|
+
...appPkg.imports,
|
|
758
|
+
"#ejected/*": "./src/ejected/*.ts"
|
|
759
|
+
},
|
|
760
|
+
dependencies: Object.fromEntries(Object.entries(deps).sort(([a], [b]) => a.localeCompare(b))),
|
|
761
|
+
devDependencies: devDeps
|
|
762
|
+
};
|
|
763
|
+
await writeFile(appPkgPath, `${JSON.stringify(nextPkg, null, " ")}\n`);
|
|
764
|
+
const configPath = join(project.root, "tatara.json");
|
|
765
|
+
const config = await readJson(configPath);
|
|
766
|
+
await writeFile(configPath, `${JSON.stringify({
|
|
767
|
+
...config,
|
|
768
|
+
ejected: {
|
|
769
|
+
...config.ejected,
|
|
770
|
+
...versions
|
|
771
|
+
}
|
|
772
|
+
}, null, " ")}\n`);
|
|
773
|
+
if (opts.formatFile) for (const file of [
|
|
774
|
+
...report.copied,
|
|
775
|
+
...report.rewritten,
|
|
776
|
+
"package.json",
|
|
777
|
+
"tatara.json"
|
|
778
|
+
]) await opts.formatFile(file, project);
|
|
779
|
+
return report;
|
|
780
|
+
}
|
|
781
|
+
//#endregion
|
|
653
782
|
//#region src/paths.ts
|
|
654
783
|
/** Package root: one level above both src/ (dev) and dist/ (published, bundled). */
|
|
655
784
|
const PACKAGE_ROOT = resolve(import.meta.dirname, "..");
|
|
@@ -2580,7 +2709,7 @@ async function generate(project, request) {
|
|
|
2580
2709
|
for (const [rel, text] of rendered) {
|
|
2581
2710
|
const full = join(project.root, rel);
|
|
2582
2711
|
await mkdir(dirname(full), { recursive: true });
|
|
2583
|
-
await writeFile(full, text);
|
|
2712
|
+
await writeFile(full, pointAtEjected(text, ejectedPackages(project.config)));
|
|
2584
2713
|
}
|
|
2585
2714
|
if (!request.skipPostProcess) await postProcess(project, files);
|
|
2586
2715
|
return {
|
|
@@ -3953,7 +4082,9 @@ function createContext(project, baseline, opts = {}) {
|
|
|
3953
4082
|
};
|
|
3954
4083
|
const abs = (path) => join(project.root, path);
|
|
3955
4084
|
let rendered;
|
|
3956
|
-
const
|
|
4085
|
+
const ejected = ejectedPackages(project.config);
|
|
4086
|
+
const write = async (path, written) => {
|
|
4087
|
+
const text = path.endsWith(".ts") ? pointAtEjected(written, ejected) : written;
|
|
3957
4088
|
if (!report.written.includes(path)) {
|
|
3958
4089
|
report.written.push(path);
|
|
3959
4090
|
const before = existsSync(abs(path)) ? await readFile(abs(path), "utf8") : void 0;
|
|
@@ -4014,6 +4145,11 @@ function createContext(project, baseline, opts = {}) {
|
|
|
4014
4145
|
return (await rendered).get(path);
|
|
4015
4146
|
},
|
|
4016
4147
|
async addDependency(name, range, { dev = false } = {}) {
|
|
4148
|
+
if (ejected.includes(name.replace(/^@tatara\//, ""))) {
|
|
4149
|
+
const note = `${name} is ejected; it was not added (its code is in src/ejected)`;
|
|
4150
|
+
if (!report.notes.includes(note)) report.notes.push(note);
|
|
4151
|
+
return;
|
|
4152
|
+
}
|
|
4017
4153
|
const pkg = JSON.parse(await readFile(abs("package.json"), "utf8"));
|
|
4018
4154
|
const key = dev ? "devDependencies" : "dependencies";
|
|
4019
4155
|
pkg[key] = {
|
|
@@ -4182,6 +4318,17 @@ program.command("upgrade").description("Run this CLI version's codemods on the a
|
|
|
4182
4318
|
printUpgrade(await upgrade(project, { dryRun: flags.dryRun }), from, cliVersion());
|
|
4183
4319
|
if (!flags.dryRun && flags.verify !== false && !await verify(loadProject())) process.exitCode = 1;
|
|
4184
4320
|
});
|
|
4321
|
+
program.command("eject <package>").description("Copy a @tatara/* package's source into src/ejected; the app owns it from then on").option("--with-dependents", "also eject installed @tatara/* packages that depend on it").action(async (name, flags) => {
|
|
4322
|
+
const report = await eject(loadProject(), name, {
|
|
4323
|
+
withDependents: flags.withDependents,
|
|
4324
|
+
formatFile: biomeFormat
|
|
4325
|
+
});
|
|
4326
|
+
const names = report.ejected.map((n) => `@tatara/${n}`).join(", ");
|
|
4327
|
+
console.log(pc.green(`ejected ${names} into src/ejected (${report.copied.length} files)`));
|
|
4328
|
+
if (report.rewritten.length > 0) console.log(` ${report.rewritten.length} files now import the ejected copy`);
|
|
4329
|
+
if (report.added.length > 0) console.log(` added dependencies: ${report.added.join(", ")}`);
|
|
4330
|
+
console.log(` run your package manager's install, then verify; tatara upgrade skips ${names} from now on`);
|
|
4331
|
+
});
|
|
4185
4332
|
program.command("doctor").description("Check the toolchain, services, env and layout").option("--json", "machine-readable output").action(async (flags) => {
|
|
4186
4333
|
const checks = await doctor(loadProject());
|
|
4187
4334
|
if (flags.json) console.log(JSON.stringify(checks, null, 2));
|
|
@@ -4189,7 +4336,7 @@ program.command("doctor").description("Check the toolchain, services, env and la
|
|
|
4189
4336
|
if (checks.some((c) => !c.ok)) process.exitCode = 1;
|
|
4190
4337
|
});
|
|
4191
4338
|
program.parseAsync().catch((error) => {
|
|
4192
|
-
if (error instanceof OptionsError || error instanceof NotATataraProject || error instanceof GeneratorError || error instanceof DbError) console.error(pc.red(error.message));
|
|
4339
|
+
if (error instanceof OptionsError || error instanceof EjectError || error instanceof NotATataraProject || error instanceof GeneratorError || error instanceof DbError) console.error(pc.red(error.message));
|
|
4193
4340
|
else console.error(error);
|
|
4194
4341
|
process.exit(1);
|
|
4195
4342
|
});
|
package/package.json
CHANGED
package/templates/versions.json
CHANGED
|
@@ -31,12 +31,12 @@
|
|
|
31
31
|
"@aws-sdk/s3-request-presigner": "3.1152.0",
|
|
32
32
|
"nodemailer": "10.1.1",
|
|
33
33
|
"@types/nodemailer": "8.0.2",
|
|
34
|
-
"@tatara/core": "0.1.
|
|
35
|
-
"@tatara/http": "0.1.
|
|
36
|
-
"@tatara/db": "0.1.
|
|
37
|
-
"@tatara/testing": "0.1.
|
|
38
|
-
"@tatara/auth": "0.1.
|
|
39
|
-
"@tatara/keycloak": "0.1.
|
|
34
|
+
"@tatara/core": "0.1.1",
|
|
35
|
+
"@tatara/http": "0.1.1",
|
|
36
|
+
"@tatara/db": "0.1.1",
|
|
37
|
+
"@tatara/testing": "0.1.1",
|
|
38
|
+
"@tatara/auth": "0.1.1",
|
|
39
|
+
"@tatara/keycloak": "0.1.1",
|
|
40
40
|
"@tatara/authz": "0.1.0",
|
|
41
41
|
"@tatara/storage": "0.1.0",
|
|
42
42
|
"@tatara/email": "0.1.0",
|