@byline/cli 4.19.0 → 5.1.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/lib/copied-script-drift.test.d.ts +2 -0
- package/dist/lib/copied-script-drift.test.d.ts.map +1 -0
- package/dist/lib/copied-script-drift.test.js +37 -0
- package/dist/lib/copied-script-drift.test.js.map +1 -0
- package/dist/phases/wire/server-ts.d.ts.map +1 -1
- package/dist/phases/wire/server-ts.js +72 -17
- package/dist/phases/wire/server-ts.js.map +1 -1
- package/dist/phases/wire/server-ts.test.d.ts +2 -0
- package/dist/phases/wire/server-ts.test.d.ts.map +1 -0
- package/dist/phases/wire/server-ts.test.js +37 -0
- package/dist/phases/wire/server-ts.test.js.map +1 -0
- package/dist/phases/wire/start-ts.d.ts +7 -0
- package/dist/phases/wire/start-ts.d.ts.map +1 -1
- package/dist/phases/wire/start-ts.js +76 -10
- package/dist/phases/wire/start-ts.js.map +1 -1
- package/dist/phases/wire/start-ts.test.d.ts +9 -0
- package/dist/phases/wire/start-ts.test.d.ts.map +1 -0
- package/dist/phases/wire/start-ts.test.js +50 -0
- package/dist/phases/wire/start-ts.test.js.map +1 -0
- package/dist/templates/byline-examples/scripts/import-docs.ts +13 -19
- package/dist/templates/byline-examples/scripts/lib/walk-document-status.ts +21 -0
- package/dist/templates/byline-examples/scripts/regenerate-media-operation.ts +17 -69
- package/dist/templates/byline-examples/scripts/regenerate-media.ts +20 -23
- package/dist/templates/byline-examples/seeds/site-settings.ts +10 -7
- package/dist/templates/dialects/mysql/byline/server.config.ts +15 -1
- package/dist/templates/dialects/mysql/byline-examples/server.config.ts +15 -1
- package/dist/templates/dialects/postgres/byline/server.config.ts +15 -1
- package/dist/templates/dialects/postgres/byline-examples/scripts/re-anchor.ts +15 -2
- package/dist/templates/dialects/postgres/byline-examples/server.config.ts +15 -1
- package/dist/templates/migrations/mysql/{0000_clean_magdalene.sql → 0000_kind_exodus.sql} +30 -2
- package/dist/templates/migrations/mysql/meta/_journal.json +2 -2
- package/dist/templates/migrations/postgres/{0000_quiet_johnny_blaze.sql → 0000_lively_talisman.sql} +28 -2
- package/dist/templates/migrations/postgres/meta/_journal.json +2 -2
- package/package.json +8 -8
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copied-script-drift.test.d.ts","sourceRoot":"","sources":["../../src/lib/copied-script-drift.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const cliTemplates = resolve(here, '../templates');
|
|
7
|
+
const webappByline = resolve(here, '../../../../apps/webapp/byline');
|
|
8
|
+
const exactCopies = [
|
|
9
|
+
{
|
|
10
|
+
name: 'media regeneration operation',
|
|
11
|
+
template: 'byline-examples/scripts/regenerate-media-operation.ts',
|
|
12
|
+
application: 'scripts/regenerate-media-operation.ts',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: 'media regeneration caller',
|
|
16
|
+
template: 'byline-examples/scripts/regenerate-media.ts',
|
|
17
|
+
application: 'scripts/regenerate-media.ts',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'PostgreSQL source-locale re-anchor script',
|
|
21
|
+
template: 'dialects/postgres/byline-examples/scripts/re-anchor.ts',
|
|
22
|
+
application: 'scripts/re-anchor.ts',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'singleton seed',
|
|
26
|
+
template: 'byline-examples/seeds/site-settings.ts',
|
|
27
|
+
application: 'seeds/site-settings.ts',
|
|
28
|
+
},
|
|
29
|
+
];
|
|
30
|
+
describe('R01 copied maintenance scripts', () => {
|
|
31
|
+
for (const copy of exactCopies) {
|
|
32
|
+
it(`keeps the ${copy.name} byte-for-byte equal to its application copy`, () => {
|
|
33
|
+
expect(readFileSync(resolve(webappByline, copy.application), 'utf8')).toBe(readFileSync(resolve(cliTemplates, copy.template), 'utf8'));
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
//# sourceMappingURL=copied-script-drift.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copied-script-drift.test.js","sourceRoot":"","sources":["../../src/lib/copied-script-drift.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAE7C,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AACpD,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,EAAE,cAAc,CAAC,CAAA;AAClD,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,EAAE,gCAAgC,CAAC,CAAA;AAEpE,MAAM,WAAW,GAAG;IAClB;QACE,IAAI,EAAE,8BAA8B;QACpC,QAAQ,EAAE,uDAAuD;QACjE,WAAW,EAAE,uCAAuC;KACrD;IACD;QACE,IAAI,EAAE,2BAA2B;QACjC,QAAQ,EAAE,6CAA6C;QACvD,WAAW,EAAE,6BAA6B;KAC3C;IACD;QACE,IAAI,EAAE,2CAA2C;QACjD,QAAQ,EAAE,wDAAwD;QAClE,WAAW,EAAE,sBAAsB;KACpC;IACD;QACE,IAAI,EAAE,gBAAgB;QACtB,QAAQ,EAAE,wCAAwC;QAClD,WAAW,EAAE,wBAAwB;KACtC;CACO,CAAA;AAEV,QAAQ,CAAC,gCAAgC,EAAE,GAAG,EAAE;IAC9C,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,EAAE,CAAC,aAAa,IAAI,CAAC,IAAI,8CAA8C,EAAE,GAAG,EAAE;YAC5E,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,YAAY,EAAE,IAAI,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CACxE,YAAY,CAAC,OAAO,CAAC,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,CAC3D,CAAA;QACH,CAAC,CAAC,CAAA;IACJ,CAAC;AACH,CAAC,CAAC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-ts.d.ts","sourceRoot":"","sources":["../../../src/phases/wire/server-ts.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAiB,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"server-ts.d.ts","sourceRoot":"","sources":["../../../src/phases/wire/server-ts.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAiB,MAAM,aAAa,CAAA;AAezD,eAAO,MAAM,YAAY,EAAE,OAS1B,CAAA"}
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
-
import { Project } from 'ts-morph';
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { Project, SyntaxKind } from 'ts-morph';
|
|
3
3
|
const REL = 'src/server.ts';
|
|
4
4
|
const IMPORT_SPECIFIER = '../byline/server.config';
|
|
5
5
|
const SNIPPET = `// Initialize Byline server config (DB adapter, etc.) before handling any requests.
|
|
6
6
|
import '${IMPORT_SPECIFIER}'
|
|
7
|
+
import { getBylineCore } from '@byline/core'
|
|
8
|
+
import { startBylineScheduler } from '@byline/core/scheduler'
|
|
9
|
+
declare global {
|
|
10
|
+
// biome-ignore lint: globalThis augmentation requires var rather than let
|
|
11
|
+
var __bylineSchedulerController__: ReturnType<typeof startBylineScheduler> | undefined
|
|
12
|
+
}
|
|
13
|
+
globalThis.__bylineSchedulerController__ ??= startBylineScheduler(getBylineCore())
|
|
7
14
|
`;
|
|
8
15
|
export const wireServerTs = {
|
|
9
16
|
key: 'server-ts',
|
|
10
|
-
title: `
|
|
17
|
+
title: `Initialize Byline and start its scheduler in ${REL}`,
|
|
11
18
|
async preview(ctx) {
|
|
12
19
|
return run(ctx, true);
|
|
13
20
|
},
|
|
@@ -21,29 +28,77 @@ async function run(ctx, dryRun) {
|
|
|
21
28
|
return { status: 'blocked', message: `${REL} not found — host phase should have caught this` };
|
|
22
29
|
}
|
|
23
30
|
const text = readFileSync(path, 'utf8');
|
|
24
|
-
|
|
25
|
-
return { status: 'skipped', message: `${REL}: side-effect import already present` };
|
|
26
|
-
}
|
|
27
|
-
if (dryRun) {
|
|
28
|
-
return { status: 'done', message: `${REL}: will inject \`import '${IMPORT_SPECIFIER}'\`` };
|
|
29
|
-
}
|
|
30
|
-
const project = new Project({ useInMemoryFileSystem: false, skipAddingFilesFromTsConfig: true });
|
|
31
|
+
const project = new Project({ useInMemoryFileSystem: true, skipAddingFilesFromTsConfig: true });
|
|
31
32
|
let source;
|
|
32
33
|
try {
|
|
33
|
-
source = project.
|
|
34
|
+
source = project.createSourceFile('server.ts', text);
|
|
34
35
|
}
|
|
35
36
|
catch (_e) {
|
|
36
37
|
return {
|
|
37
38
|
status: 'manual',
|
|
38
|
-
message: `${REL}: could not parse — please
|
|
39
|
+
message: `${REL}: could not parse — please wire initialization and scheduler manually`,
|
|
39
40
|
snippet: SNIPPET,
|
|
40
41
|
};
|
|
41
42
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
let changed = false;
|
|
44
|
+
if (!hasImport(text, IMPORT_SPECIFIER)) {
|
|
45
|
+
source.insertImportDeclaration(0, { moduleSpecifier: IMPORT_SPECIFIER });
|
|
46
|
+
changed = true;
|
|
47
|
+
}
|
|
48
|
+
const schedulerImport = source
|
|
49
|
+
.getImportDeclarations()
|
|
50
|
+
.find((declaration) => declaration.getModuleSpecifierValue() === '@byline/core/scheduler');
|
|
51
|
+
const schedulerName = schedulerImport
|
|
52
|
+
?.getNamedImports()
|
|
53
|
+
.find((item) => item.getName() === 'startBylineScheduler');
|
|
54
|
+
const localSchedulerName = schedulerName?.getAliasNode()?.getText() ?? schedulerName?.getName() ?? 'startBylineScheduler';
|
|
55
|
+
const alreadyStarted = schedulerName !== undefined &&
|
|
56
|
+
source
|
|
57
|
+
.getDescendantsOfKind(SyntaxKind.CallExpression)
|
|
58
|
+
.some((call) => call.getExpression().getText() === localSchedulerName);
|
|
59
|
+
if (!alreadyStarted) {
|
|
60
|
+
const coreGetter = ensureImport(source, '@byline/core', 'getBylineCore');
|
|
61
|
+
const scheduler = ensureImport(source, '@byline/core/scheduler', 'startBylineScheduler');
|
|
62
|
+
const declaration = source
|
|
63
|
+
.getDescendantsOfKind(SyntaxKind.VariableDeclaration)
|
|
64
|
+
.some((item) => item.getName() === '__bylineSchedulerController__');
|
|
65
|
+
source.addStatements(`
|
|
66
|
+
// Host-owned lifetime: config imports from seeds/migrations remain inert.
|
|
67
|
+
${declaration
|
|
68
|
+
? ''
|
|
69
|
+
: `declare global {
|
|
70
|
+
// biome-ignore lint: globalThis augmentation requires var rather than let
|
|
71
|
+
var __bylineSchedulerController__: ReturnType<typeof ${scheduler}> | undefined
|
|
72
|
+
}`}
|
|
73
|
+
globalThis.__bylineSchedulerController__ ??= ${scheduler}(${coreGetter}())
|
|
74
|
+
`);
|
|
75
|
+
changed = true;
|
|
76
|
+
}
|
|
77
|
+
if (!changed)
|
|
78
|
+
return {
|
|
79
|
+
status: 'skipped',
|
|
80
|
+
message: `${REL}: Byline initialization and scheduler already present`,
|
|
81
|
+
};
|
|
82
|
+
if (!dryRun) {
|
|
83
|
+
writeFileSync(path, source.getFullText());
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
status: 'done',
|
|
87
|
+
message: `${REL}: ${dryRun ? 'will wire' : 'wired'} Byline initialization and scheduler`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function ensureImport(source, moduleSpecifier, name) {
|
|
91
|
+
const declaration = source
|
|
92
|
+
.getImportDeclarations()
|
|
93
|
+
.find((item) => item.getModuleSpecifierValue() === moduleSpecifier && !item.isTypeOnly());
|
|
94
|
+
const existing = declaration?.getNamedImports().find((item) => item.getName() === name);
|
|
95
|
+
if (existing)
|
|
96
|
+
return existing.getAliasNode()?.getText() ?? existing.getName();
|
|
97
|
+
if (declaration)
|
|
98
|
+
declaration.addNamedImport(name);
|
|
99
|
+
else
|
|
100
|
+
source.addImportDeclaration({ moduleSpecifier, namedImports: [name] });
|
|
101
|
+
return name;
|
|
47
102
|
}
|
|
48
103
|
function hasImport(source, specifier) {
|
|
49
104
|
// Match either form: with or without `.ts` extension, single or double quote.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-ts.js","sourceRoot":"","sources":["../../../src/phases/wire/server-ts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"server-ts.js","sourceRoot":"","sources":["../../../src/phases/wire/server-ts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-ts.test.d.ts","sourceRoot":"","sources":["../../../src/phases/wire/server-ts.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
3
|
+
import { createTestContext } from '../../test-helpers.js';
|
|
4
|
+
import { wireServerTs } from './server-ts.js';
|
|
5
|
+
const contexts = [];
|
|
6
|
+
afterEach(() => {
|
|
7
|
+
for (const ctx of contexts.splice(0))
|
|
8
|
+
rmSync(ctx.cwd, { recursive: true, force: true });
|
|
9
|
+
});
|
|
10
|
+
function fixture(text) {
|
|
11
|
+
const ctx = createTestContext();
|
|
12
|
+
contexts.push(ctx);
|
|
13
|
+
mkdirSync(ctx.resolve('src'), { recursive: true });
|
|
14
|
+
writeFileSync(ctx.resolve('src/server.ts'), text);
|
|
15
|
+
return ctx;
|
|
16
|
+
}
|
|
17
|
+
describe('host scheduler wiring', () => {
|
|
18
|
+
it('upgrades an existing config import, preserves the handler, and is idempotent', async () => {
|
|
19
|
+
const ctx = fixture("import '../byline/server.config.ts'\nexport default handler");
|
|
20
|
+
const original = readFileSync(ctx.resolve('src/server.ts'), 'utf8');
|
|
21
|
+
expect((await wireServerTs.preview(ctx)).status).toBe('done');
|
|
22
|
+
expect(readFileSync(ctx.resolve('src/server.ts'), 'utf8')).toBe(original);
|
|
23
|
+
expect((await wireServerTs.apply(ctx)).status).toBe('done');
|
|
24
|
+
const text = readFileSync(ctx.resolve('src/server.ts'), 'utf8');
|
|
25
|
+
expect(text).toContain('globalThis.__bylineSchedulerController__ ??= startBylineScheduler(getBylineCore())');
|
|
26
|
+
expect(text).toContain('export default handler');
|
|
27
|
+
expect((await wireServerTs.apply(ctx)).status).toBe('skipped');
|
|
28
|
+
expect(readFileSync(ctx.resolve('src/server.ts'), 'utf8')).toBe(text);
|
|
29
|
+
});
|
|
30
|
+
it('preserves an existing aliased scheduler startup', async () => {
|
|
31
|
+
const text = "import '../byline/server.config'\nimport { startBylineScheduler as start } from '@byline/core/scheduler'\nstart(core, { concurrency: 2 })";
|
|
32
|
+
const ctx = fixture(text);
|
|
33
|
+
expect((await wireServerTs.apply(ctx)).status).toBe('skipped');
|
|
34
|
+
expect(readFileSync(ctx.resolve('src/server.ts'), 'utf8')).toBe(text);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
//# sourceMappingURL=server-ts.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-ts.test.js","sourceRoot":"","sources":["../../../src/phases/wire/server-ts.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAExE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAExD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAG7C,MAAM,QAAQ,GAAc,EAAE,CAAA;AAC9B,SAAS,CAAC,GAAG,EAAE;IACb,KAAK,MAAM,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;QAAE,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;AACzF,CAAC,CAAC,CAAA;AACF,SAAS,OAAO,CAAC,IAAY;IAC3B,MAAM,GAAG,GAAG,iBAAiB,EAAE,CAAA;IAC/B,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAClB,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAClD,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,IAAI,CAAC,CAAA;IACjD,OAAO,GAAG,CAAA;AACZ,CAAC;AACD,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,EAAE,CAAC,8EAA8E,EAAE,KAAK,IAAI,EAAE;QAC5F,MAAM,GAAG,GAAG,OAAO,CAAC,6DAA6D,CAAC,CAAA;QAClF,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC,CAAA;QACnE,MAAM,CAAC,CAAC,MAAM,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAC7D,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QACzE,MAAM,CAAC,CAAC,MAAM,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAC3D,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC,CAAA;QAC/D,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CACpB,oFAAoF,CACrF,CAAA;QACD,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,wBAAwB,CAAC,CAAA;QAChD,MAAM,CAAC,CAAC,MAAM,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QAC9D,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACvE,CAAC,CAAC,CAAA;IACF,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;QAC/D,MAAM,IAAI,GACR,2IAA2I,CAAA;QAC7I,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;QACzB,MAAM,CAAC,CAAC,MAAM,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QAC9D,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACvE,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
1
8
|
import type { SubEdit } from './shared.js';
|
|
2
9
|
export declare const wireStartTs: SubEdit;
|
|
3
10
|
//# sourceMappingURL=start-ts.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start-ts.d.ts","sourceRoot":"","sources":["../../../src/phases/wire/start-ts.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"start-ts.d.ts","sourceRoot":"","sources":["../../../src/phases/wire/start-ts.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAeH,OAAO,KAAK,EAAE,OAAO,EAAiB,MAAM,aAAa,CAAA;AAiBzD,eAAO,MAAM,WAAW,EAAE,OASzB,CAAA"}
|
|
@@ -1,14 +1,24 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync } from 'node:fs';
|
|
2
9
|
import { Node, Project, SyntaxKind, } from 'ts-morph';
|
|
3
10
|
const REL = 'src/start.ts';
|
|
4
11
|
const ADAPTER_NAME = 'bylineCodedErrorAdapter';
|
|
5
12
|
const ADAPTER_MODULE = '@byline/host-tanstack-start/integrations/start-errors';
|
|
6
|
-
const SNIPPET = `import { createStart } from '@tanstack/react-start'
|
|
13
|
+
const SNIPPET = `import { createCsrfMiddleware, createStart } from '@tanstack/react-start'
|
|
7
14
|
|
|
8
15
|
import { ${ADAPTER_NAME} } from '${ADAPTER_MODULE}'
|
|
16
|
+
import { passwordSignInMiddleware } from '@byline/host-tanstack-start/integrations/sign-in-middleware'
|
|
17
|
+
import { sessionRequestMiddleware } from '@byline/host-tanstack-start/integrations/session-request-middleware'
|
|
9
18
|
|
|
10
19
|
export const startInstance = createStart(() => ({
|
|
11
20
|
serializationAdapters: [${ADAPTER_NAME}],
|
|
21
|
+
requestMiddleware: [createCsrfMiddleware({ filter: (ctx) => ctx.handlerType === 'serverFn' }), sessionRequestMiddleware, passwordSignInMiddleware],
|
|
12
22
|
}))
|
|
13
23
|
`;
|
|
14
24
|
export const wireStartTs = {
|
|
@@ -26,13 +36,6 @@ async function run(ctx, dryRun) {
|
|
|
26
36
|
if (!existsSync(path)) {
|
|
27
37
|
return { status: 'blocked', message: `${REL} not found — host phase should have caught this` };
|
|
28
38
|
}
|
|
29
|
-
const text = readFileSync(path, 'utf8');
|
|
30
|
-
// Cheap pre-check: if the adapter identifier is already referenced anywhere,
|
|
31
|
-
// assume the wiring is in place. Avoids a full AST round-trip on the common
|
|
32
|
-
// case where wire has already been run.
|
|
33
|
-
if (text.includes(ADAPTER_NAME)) {
|
|
34
|
-
return { status: 'skipped', message: `${REL}: ${ADAPTER_NAME} already registered` };
|
|
35
|
-
}
|
|
36
39
|
const project = new Project({ useInMemoryFileSystem: false, skipAddingFilesFromTsConfig: true });
|
|
37
40
|
let source;
|
|
38
41
|
try {
|
|
@@ -49,13 +52,76 @@ async function run(ctx, dryRun) {
|
|
|
49
52
|
if (!optionsLiteral) {
|
|
50
53
|
return manualBail(`${REL}: \`createStart\` factory does not return an inline object literal — cannot safely auto-edit`);
|
|
51
54
|
}
|
|
55
|
+
const middlewareProp = optionsLiteral.getProperty('requestMiddleware');
|
|
56
|
+
if (middlewareProp &&
|
|
57
|
+
(!Node.isPropertyAssignment(middlewareProp) ||
|
|
58
|
+
!Node.isArrayLiteralExpression(middlewareProp.getInitializer()))) {
|
|
59
|
+
return manualBail(`${REL}: requestMiddleware must be an inline array for safe editing`);
|
|
60
|
+
}
|
|
61
|
+
const arr = middlewareProp && Node.isPropertyAssignment(middlewareProp)
|
|
62
|
+
? middlewareProp.getInitializerIfKindOrThrow(SyntaxKind.ArrayLiteralExpression)
|
|
63
|
+
: undefined;
|
|
64
|
+
const hasCsrf = arr?.getElements().some((el) => {
|
|
65
|
+
const expression = Node.isIdentifier(el)
|
|
66
|
+
? source.getVariableDeclaration(el.getText())?.getInitializer()
|
|
67
|
+
: el;
|
|
68
|
+
return (expression &&
|
|
69
|
+
Node.isCallExpression(expression) &&
|
|
70
|
+
expression.getExpression().getText() === 'createCsrfMiddleware');
|
|
71
|
+
}) ?? false;
|
|
72
|
+
const hasSessionGuard = arr?.getElements().some((el) => el.getText() === 'sessionRequestMiddleware') ?? false;
|
|
73
|
+
const hasBodyGuard = arr?.getElements().some((el) => el.getText() === 'passwordSignInMiddleware') ?? false;
|
|
74
|
+
const adapterProp = optionsLiteral.getProperty('serializationAdapters');
|
|
75
|
+
const hasAdapter = adapterProp &&
|
|
76
|
+
Node.isPropertyAssignment(adapterProp) &&
|
|
77
|
+
adapterProp
|
|
78
|
+
.getInitializerIfKind(SyntaxKind.ArrayLiteralExpression)
|
|
79
|
+
?.getElements()
|
|
80
|
+
.some((el) => el.getText() === ADAPTER_NAME);
|
|
81
|
+
if (hasCsrf && hasBodyGuard && hasSessionGuard && hasAdapter) {
|
|
82
|
+
return {
|
|
83
|
+
status: 'skipped',
|
|
84
|
+
message: `${REL}: sign-in protection and error serialization already registered`,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
52
87
|
if (dryRun) {
|
|
53
88
|
return {
|
|
54
89
|
status: 'done',
|
|
55
|
-
message: `${REL}: will
|
|
90
|
+
message: `${REL}: will register sign-in protection and error serialization`,
|
|
56
91
|
};
|
|
57
92
|
}
|
|
58
93
|
ensureImport(source);
|
|
94
|
+
for (const [moduleSpecifier, name] of [
|
|
95
|
+
['@byline/host-tanstack-start/integrations/sign-in-middleware', 'passwordSignInMiddleware'],
|
|
96
|
+
['@tanstack/react-start', 'createCsrfMiddleware'],
|
|
97
|
+
[
|
|
98
|
+
'@byline/host-tanstack-start/integrations/session-request-middleware',
|
|
99
|
+
'sessionRequestMiddleware',
|
|
100
|
+
],
|
|
101
|
+
]) {
|
|
102
|
+
const declaration = source
|
|
103
|
+
.getImportDeclarations()
|
|
104
|
+
.find((d) => d.getModuleSpecifierValue() === moduleSpecifier);
|
|
105
|
+
if (!declaration)
|
|
106
|
+
source.addImportDeclaration({ moduleSpecifier: moduleSpecifier, namedImports: [name] });
|
|
107
|
+
else if (!declaration.getNamedImports().some((n) => n.getText() === name))
|
|
108
|
+
declaration.addNamedImport(name);
|
|
109
|
+
}
|
|
110
|
+
const csrf = "createCsrfMiddleware({ filter: (ctx) => ctx.handlerType === 'serverFn' })";
|
|
111
|
+
if (!arr) {
|
|
112
|
+
optionsLiteral.addPropertyAssignment({
|
|
113
|
+
name: 'requestMiddleware',
|
|
114
|
+
initializer: `[${csrf}, sessionRequestMiddleware, passwordSignInMiddleware]`,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
if (!hasCsrf)
|
|
119
|
+
arr.insertElement(0, csrf);
|
|
120
|
+
if (!hasSessionGuard)
|
|
121
|
+
arr.addElement('sessionRequestMiddleware');
|
|
122
|
+
if (!hasBodyGuard)
|
|
123
|
+
arr.addElement('passwordSignInMiddleware');
|
|
124
|
+
}
|
|
59
125
|
ensureAdapterInOptions(optionsLiteral);
|
|
60
126
|
source.saveSync();
|
|
61
127
|
return { status: 'done', message: `${REL}: registered ${ADAPTER_NAME}` };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start-ts.js","sourceRoot":"","sources":["../../../src/phases/wire/start-ts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,
|
|
1
|
+
{"version":3,"file":"start-ts.js","sourceRoot":"","sources":["../../../src/phases/wire/start-ts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=start-ts.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"start-ts.test.d.ts","sourceRoot":"","sources":["../../../src/phases/wire/start-ts.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
10
|
+
import { createTestContext } from '../../test-helpers.js';
|
|
11
|
+
import { wireStartTs } from './start-ts.js';
|
|
12
|
+
const contexts = [];
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
for (const ctx of contexts.splice(0))
|
|
15
|
+
rmSync(ctx.cwd, { recursive: true, force: true });
|
|
16
|
+
});
|
|
17
|
+
function fixture(options) {
|
|
18
|
+
const ctx = createTestContext();
|
|
19
|
+
contexts.push(ctx);
|
|
20
|
+
mkdirSync(ctx.resolve('src'), { recursive: true });
|
|
21
|
+
writeFileSync(ctx.resolve('src/start.ts'), `import { createStart, createCsrfMiddleware } from '@tanstack/react-start'
|
|
22
|
+
import { bylineCodedErrorAdapter } from '@byline/host-tanstack-start/integrations/start-errors'
|
|
23
|
+
const csrf = createCsrfMiddleware({filter: ctx => ctx.handlerType === 'serverFn'})
|
|
24
|
+
export const startInstance = createStart(() => (${options}))`);
|
|
25
|
+
return ctx;
|
|
26
|
+
}
|
|
27
|
+
describe('Start sign-in middleware wiring', () => {
|
|
28
|
+
it('upgrades existing serialization-only wiring and is idempotent', async () => {
|
|
29
|
+
const ctx = fixture('{serializationAdapters:[bylineCodedErrorAdapter]}');
|
|
30
|
+
expect((await wireStartTs.apply(ctx)).status).toBe('done');
|
|
31
|
+
const text = readFileSync(ctx.resolve('src/start.ts'), 'utf8');
|
|
32
|
+
expect(text).toContain('requestMiddleware: [createCsrfMiddleware(');
|
|
33
|
+
expect(text).toContain('passwordSignInMiddleware]');
|
|
34
|
+
expect((await wireStartTs.apply(ctx)).status).toBe('skipped');
|
|
35
|
+
expect(readFileSync(ctx.resolve('src/start.ts'), 'utf8')).toBe(text);
|
|
36
|
+
});
|
|
37
|
+
it('preserves existing CSRF and unrelated middleware', async () => {
|
|
38
|
+
const ctx = fixture('{serializationAdapters:[bylineCodedErrorAdapter],requestMiddleware:[csrf, custom]}');
|
|
39
|
+
expect((await wireStartTs.apply(ctx)).status).toBe('done');
|
|
40
|
+
expect(readFileSync(ctx.resolve('src/start.ts'), 'utf8')).toContain('[csrf, custom, sessionRequestMiddleware, passwordSignInMiddleware]');
|
|
41
|
+
});
|
|
42
|
+
it('leaves computed middleware configuration for manual wiring', async () => {
|
|
43
|
+
const ctx = fixture('{requestMiddleware: buildMiddleware()}');
|
|
44
|
+
const original = readFileSync(ctx.resolve('src/start.ts'), 'utf8');
|
|
45
|
+
expect((await wireStartTs.preview(ctx)).status).toBe('manual');
|
|
46
|
+
expect((await wireStartTs.apply(ctx)).status).toBe('manual');
|
|
47
|
+
expect(readFileSync(ctx.resolve('src/start.ts'), 'utf8')).toBe(original);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
//# sourceMappingURL=start-ts.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"start-ts.test.js","sourceRoot":"","sources":["../../../src/phases/wire/start-ts.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { walkToStatus } from './lib/walk-document-status.js'
|
|
1
2
|
/**
|
|
2
3
|
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
4
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
@@ -185,25 +186,13 @@ function derivePath(frontmatter: DocFrontmatter, locale: string): string {
|
|
|
185
186
|
* through any intermediate statuses (e.g. needs_review). No-op when the
|
|
186
187
|
* workflow doesn't include the target or when already at/past it.
|
|
187
188
|
*/
|
|
188
|
-
|
|
189
|
-
handle: DocsHandle,
|
|
190
|
-
documentId: string,
|
|
191
|
-
workflowStatuses: readonly { name: string }[],
|
|
192
|
-
currentStatus: string,
|
|
193
|
-
targetStatus: string
|
|
194
|
-
): Promise<void> {
|
|
195
|
-
const currentIdx = workflowStatuses.findIndex((s) => s.name === currentStatus)
|
|
196
|
-
const targetIdx = workflowStatuses.findIndex((s) => s.name === targetStatus)
|
|
197
|
-
if (currentIdx === -1 || targetIdx === -1 || targetIdx <= currentIdx) return
|
|
198
|
-
for (let i = currentIdx + 1; i <= targetIdx; i++) {
|
|
199
|
-
await handle.changeStatus(documentId, workflowStatuses[i].name)
|
|
200
|
-
}
|
|
201
|
-
}
|
|
189
|
+
|
|
202
190
|
|
|
203
191
|
interface ProcessResult {
|
|
204
192
|
filePath: string
|
|
205
193
|
action: 'created' | 'updated' | 'skipped'
|
|
206
194
|
documentId?: string
|
|
195
|
+
revision?: number
|
|
207
196
|
path: string
|
|
208
197
|
}
|
|
209
198
|
|
|
@@ -301,18 +290,22 @@ async function processFile(
|
|
|
301
290
|
const workflowStatuses = definition?.workflow?.statuses ?? []
|
|
302
291
|
const defaultStatus = workflowStatuses[0]?.name ?? 'draft'
|
|
303
292
|
|
|
304
|
-
const
|
|
293
|
+
const selected = await handle.findByPath(docPath, {
|
|
305
294
|
locale,
|
|
306
295
|
status: 'any',
|
|
307
296
|
_bypassBeforeRead: true,
|
|
308
297
|
})
|
|
309
298
|
|
|
299
|
+
const existing = selected == null ? null : await handle.findByIdForEdit(selected.id, { locale, _bypassBeforeRead: true })
|
|
300
|
+
if (selected != null && existing == null) throw new Error('Import target is no longer available; rerun the import.')
|
|
301
|
+
|
|
310
302
|
if (existing) {
|
|
311
303
|
// Don't clobber publishedOn if Byline already has one.
|
|
312
304
|
if (existing.fields?.publishedOn) {
|
|
313
305
|
delete payload.publishedOn
|
|
314
306
|
}
|
|
315
307
|
const result = await handle.update(existing.id, payload, {
|
|
308
|
+
expectedRevision: existing.revision,
|
|
316
309
|
locale,
|
|
317
310
|
// Advertise the imported locale (editorial available-locales set), merged
|
|
318
311
|
// with whatever is already advertised so a later-locale re-import doesn't
|
|
@@ -320,8 +313,8 @@ async function processFile(
|
|
|
320
313
|
// completeness ledger (intersection). See docs/08-internationalization/index.md.
|
|
321
314
|
availableLocales: [...new Set([...(existing.availableLocales ?? []), locale])],
|
|
322
315
|
})
|
|
323
|
-
await walkToStatus(handle, result.documentId, workflowStatuses, defaultStatus, desiredStatus)
|
|
324
|
-
return { filePath, action: 'updated', documentId: result.documentId, path: docPath }
|
|
316
|
+
const revision = await walkToStatus(handle, result.documentId, workflowStatuses, defaultStatus, desiredStatus, result.revision)
|
|
317
|
+
return { filePath, action: 'updated', documentId: result.documentId, revision, path: docPath }
|
|
325
318
|
}
|
|
326
319
|
|
|
327
320
|
const result = await handle.create(payload, {
|
|
@@ -332,7 +325,7 @@ async function processFile(
|
|
|
332
325
|
// intersection of this editorial set with the completeness ledger.
|
|
333
326
|
availableLocales: [locale],
|
|
334
327
|
})
|
|
335
|
-
return { filePath, action: 'created', documentId: result.documentId, path: docPath }
|
|
328
|
+
return { filePath, action: 'created', documentId: result.documentId, revision: result.revision, path: docPath }
|
|
336
329
|
}
|
|
337
330
|
|
|
338
331
|
/**
|
|
@@ -386,7 +379,8 @@ async function placeTreeFromDirectories(
|
|
|
386
379
|
const groupKey = parentDocumentId ?? ROOT_GROUP
|
|
387
380
|
const beforeDocumentId = lastSiblingByGroup.get(groupKey) ?? null
|
|
388
381
|
try {
|
|
389
|
-
|
|
382
|
+
if (r.revision == null) throw new Error('Imported document has no observed revision')
|
|
383
|
+
await handle.placeTreeNode(r.documentId, { expectedRevision: r.revision, parentDocumentId, beforeDocumentId })
|
|
390
384
|
lastSiblingByGroup.set(groupKey, r.documentId)
|
|
391
385
|
if (parentDocumentId != null) {
|
|
392
386
|
placed += 1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CollectionHandle } from '@byline/client'
|
|
2
|
+
|
|
3
|
+
/** Preserve the revision returned by each successful workflow transition.
|
|
4
|
+
* A conflict terminates the walk; it never fetches a newer observation. */
|
|
5
|
+
export async function walkToStatus(
|
|
6
|
+
handle: Pick<CollectionHandle, 'changeStatus'>,
|
|
7
|
+
documentId: string,
|
|
8
|
+
workflowStatuses: readonly { name: string }[],
|
|
9
|
+
currentStatus: string,
|
|
10
|
+
targetStatus: string,
|
|
11
|
+
expectedRevision: number
|
|
12
|
+
): Promise<number> {
|
|
13
|
+
const currentIdx = workflowStatuses.findIndex((s) => s.name === currentStatus)
|
|
14
|
+
const targetIdx = workflowStatuses.findIndex((s) => s.name === targetStatus)
|
|
15
|
+
if (currentIdx === -1 || targetIdx === -1 || targetIdx <= currentIdx) return expectedRevision
|
|
16
|
+
for (let i = currentIdx + 1; i <= targetIdx; i++) {
|
|
17
|
+
const result = await handle.changeStatus(documentId, workflowStatuses[i].name, { expectedRevision })
|
|
18
|
+
expectedRevision = result.revision
|
|
19
|
+
}
|
|
20
|
+
return expectedRevision
|
|
21
|
+
}
|